添加了接口/继承维度的可选参数C#的另一个范例

时间:2013-11-19 09:20:24

标签: c# inheritance interface optional-parameters

在使用可选参数与C#中的方法覆盖和接口串联时遇到了棘手的情况。我看过this

只是想为整个图片添加另一个维度。该帖子中有相当多的代码插图。我通过VS1选择了涉及标签的那个,并为它添加了另一个维度,因为它具有接口以及正在演示的继承。虽然在那里发布的代码确实可以工作并显示在子类,基类和接口中找到的相应字符串,但以下代码却没有。

void Main()
{
    SubTag subTag = new SubTag();
    ITag subTagOfInterfaceType = new SubTag();
    BaseTag subTagOfBaseType = new SubTag();

    subTag.WriteTag();
    subTagOfInterfaceType.WriteTag();
    subTagOfBaseType.WriteTag();
}

public interface ITag
{
    void WriteTag(string tagName = "ITag");
}
public class BaseTag :ITag
{
    public virtual void WriteTag(string tagName = "BaseTag") { Console.WriteLine(tagName); }
}

public class SubTag : BaseTag
{
    public override void WriteTag(string tagName = "SubTag") { Console.WriteLine(tagName); }
}

输出

SubTag
ITag
BaseTag

因此,似乎持有对继承/实现的子类的引用的引用类型在确定拾取哪个可选参数值时很重要。

有没有人遇到类似问题并找到解决方案?或者C#在以后的版本中有一些解决方法吗? (我使用的是4.0)

感谢。

3 个答案:

答案 0 :(得分:0)

我认为可选参数只是语法糖。所以他们在编译时被选中。编译器不知道对象的实际类型,因此根据引用的类型选择可选值。

如果您需要此行为,那么您可以提供两种不同的方法,一种方法使用参数,另一种方法不使用,然后您可以在不同的实现中以不同方式实现无参数方法。当然,这仅适用于固定参数布局。

更新:测试并确认,给定void x(int z = 8)方法,方法调用x()编译为x(8),因此参数值以常量形式出现。

答案 1 :(得分:0)

解决这个问题的一个常见方法是拥有一个特殊的"哨兵"值(通常为null),实现方法识别并替换为所需的值。

对于您的示例,它可能看起来像这样:

public interface ITag
{
    void WriteTag(string tagName = null);
}

public class BaseTag :ITag
{
    public virtual void WriteTag(string tagName = null)
    {
        if (tagName == null)
            tagName = "BaseTag";

        Console.WriteLine(tagName); 
    }
}

public class SubTag : BaseTag
{
    public override void WriteTag(string tagName = null)
    {
        if (tagName == null)
            tagName = "SubTag";

        Console.WriteLine(tagName);
    }
}

然后您的测试代码将输出

SubTag
SubTag
SubTag

我认为你想要的是什么?

答案 2 :(得分:0)

C#团队不喜欢在语言中添加可选参数,这是一个很好的演示原因。

有助于了解它们的实施方式。 CLR完全没有注意到这个特性,它是由编译器实现的。如果使用缺少的参数编写方法调用,那么C#编译器实际上会为方法调用生成带有参数的代码,并传递默认值。使用ildasm.exe很容易看到。

您可以在语言规则中看到这一点,可选值必须是常量表达式。或者换句话说,可以在编译时确定的值。您不能使用 new 关键字或使用变量的表达式。必需,因此编译器可以在程序集元数据中嵌入默认值。当它编译对具有在另一个程序集中声明的可选参数的方法的调用时,它将再次需要它。

这里的摩擦是编译器无法确定在运行时实际调用哪个虚拟方法。动态分派是纯运行时功能。

所以它可以合理地通过声明类型的对象引用。您使用了所有三个版本,因此您获得了所有三个默认参数值。