MarkupExtension声明中的默认构造函数参数

Default Constructor Parameter in MarkupExtension declaration
2021-02-23
  •  译文(汉语)
  •  原文(英语)

将这个问题减少到最低限度,请考虑这个MarkupExtension类.

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;
    }
}

声明此Xaml时...

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

...您会按预期在TextBlock中看到"helloworld".至此一切顺利.

但是将构造函数参数更改为此...

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

...以及与此相关的Xaml ...

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

结果错误消息是...

No constructor for type 'ProblemStatement' has 1 parameters.

有一种解决方法,可以通过将该语句添加到类中来链接构造函数.

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

这将在TextBlock中显示"hellonot提供".但是,这也改变了MarkupExtension的语义,在较大的"实际"情况下不希望这样做.当使用更复杂的类型或构造函数参数为"动态"类型时,重载的复杂性也会大大增加.另外,例如,完全禁止使用新的"呼叫者信息"属性.

所以问题是:如何声明Xaml,以便Xaml解析器使用默认的构造函数参数?

解决过程1

试试看:

    public string Optional{ get; set; } = "DefaultValue";

    private readonly string _mandatory;

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

用法:

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

选择:

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

结果:

  • 没有XAML解析错误
  • 无需为可选参数重载构造函数
  • 强制参数是构造函数参数.
  • 可选参数是属性.
速聊1:
ConstructorArgumentAttribute由于命名的属性Optional实际上不是构造函数参数,因此在这种情况下有什么好处?我以为ConstructorArgumentAttribute是为了更好地告知XAML序列化有关实际构造函数arg与属性之间的关系.
速聊2:
空无一人.当时我对这个属性有错误的印象.答案进行了相应的编辑.

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?

Solutions1

Try this out:

    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 properties.
Talk1:
What benefit does ConstructorArgumentAttribute provide in this case since the property named Optional isn't actually a constructor argument? I thought ConstructorArgumentAttribute was intended to better inform XAML Serialization about the relationship between an actual constructor arg and a property.
Talk2:
There is none. At the time I had the wrong impression of this attribute. answer edited accordingly.
转载于:https://stackoverflow.com/questions/23711972/default-constructor-parameter-in-markupextension-declaration

本人是.net程序员,因为英语不行,使用工具翻译,希望对有需要的人有所帮助
如果本文质量不好,还请谅解,毕竟这些操作还是比较费时的,英语较好的可以看原文

留言回复
我们只提供高质量资源,素材,源码,坚持 下了就能用 原则,让客户花了钱觉得值
上班时间 : 周一至周五9:00-17:30 期待您的加入