Gayot Fow Gayot Fow - 25 days ago 13
C# Question

Default Constructor Parameter in MarkupExtension declaration

Reducing this question to the bare minimum, consider this MarkupExtension class...

public class ProblemStatement : MarkupExtension
{
private readonly string _first;
private readonly string _second;
public ProblemStatement(string first, string second)
{
_first = first;
_second = second;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public override string ToString()
{
return _first + _second;
}
}


When this Xaml is declared...

<Grid>
<TextBlock Name="TextBlock1" Tag="{so:ProblemStatement 'hello', 'world'}"/>
<TextBlock Text="{Binding ElementName=TextBlock1, Path=Tag}"/>
</Grid>


...you see 'helloworld' in the TextBlock as expected. All's well to this point.

But changing the constructor parameter to this...

public ProblemStatement(string first, string second = "nothing")


...and the relevant Xaml to this...

<Grid>
<TextBlock Name="TextBlock1" Tag="{so:ProblemStatement 'hello'}"/>
<TextBlock Text="{Binding ElementName=TextBlock1, Path=Tag}"/>
</Grid>


the resulting error message is...

No constructor for type 'ProblemStatement' has 1 parameters.


There is a work-around, which is to chain the constructor by adding this statement to the class...

public ProblemStatement(string first) : this(first, "not provided") { }


and this will show 'hellonot provided' in the TextBlock. However, this also changes the semantics of the MarkupExtension and is not desirable in the larger, 'real-world' case. Also the complexity of overloading increases dramatically when more complex types are used or the constructor arguments are of type 'dynamic'. Also, for example, use of the new 'Caller Information' attributes is blocked altogether.

So the question is: how to declare the Xaml so that the Xaml parser will honour a default constructor argument?

Answer

Try this out:

    [System.Windows.Markup.ConstructorArgument(nameof(Optional))]
    public string Optional{ get; set; } = "DefaultValue";

    private readonly string _mandatory;

    public ProblemStatement(string mandatory)
    {
        _mandatory = mandatory;
    }

Usage:

<TextBlock Name="TextBlock1" Tag="{local:ProblemStatement 'hello', Optional=NotDefault}"/>

Alternative:

<TextBlock Name="TextBlock1" Tag="{local:ProblemStatement 'hello'}"/>

Result:

  • No XAML parsing errors
  • No need to overload the constructor for optional parameters
  • Mandatory parameters are constructor parameters.
  • Optional parameters are decorated with the latter attribute.
Comments