【问题标题】:C# Covariance and Contravariance when implementing interfaces实现接口时的 C# 协变和逆变
【发布时间】:2011-10-26 15:19:03
【问题描述】:

我最近决定刷新关于 C# 基础的记忆,所以这可能是微不足道的,但我遇到了以下问题:

StringCollection 在 .NET v1.0 中用于为字符串创建强类型集合,而不是基于 object 的 ArrayList(后来通过包含通用集合进行了增强):

快速浏览StringCollection的定义,可以看到以下内容:

// Summary:
//     Represents a collection of strings.
[Serializable]
public class StringCollection : IList, ICollection, IEnumerable
{
...
    public int Add(string value);
...
}

您可以看到它实现了IList,其中包含以下声明(以及其他一些声明):

int Add(object value);

但不是:

int Add(string value);

我的第一个假设是,由于 .NET 框架协方差规则,这是可能的。

所以为了确保,我尝试编写自己的类来实现 IList 并进行了更改

int Add(object value);

检索字符串类型而不是对象类型,但令我惊讶的是,在尝试编译项目时,我得到了一个编译时错误:

does not implement interface member 'System.Collections.IList.Add(object)'

任何想法是什么原因造成的?

谢谢!

【问题讨论】:

    标签: c# .net inheritance interface covariance


    【解决方案1】:

    该行为是由IList.Add(object) 的显式实现引起的,而不是由协/逆变引起的。根据 MSDN 文档,StringCollection 显式实现了IList.Add(object); Add(string) 方法无关。实现可能类似于这样:

    class StringCollection : IList
    {
        ...
        public int Add(string value)
        {} // implementation
    
        public int IList.Add (object value)
        {
            if (!value is string)) return -1;
            return Add(value as string)
        }
    }
    

    可以观察到这种区别:

      StringCollection collection = new StringCollection();
      collection.Add(1); // compile error
      (collection as IList).Add(1); // compiles, runtime error
      (collection as IList).Add((object)"") // calls interface method, which adds string to collection
    

    附录

    上面没有说明为什么要实现这种模式。 C# 语言规范指出 [§13.4.1,强调添加]:

    在某些情况下,接口成员的名称可能不合适 对于实现类,在这种情况下,接口成员可能是 使用显式接口成员实现来实现。 [...]

    不可能通过方法调用、属性访问或索引器访问中的完全限定名称访问显式接口成员实现。只能通过接口访问显式接口成员实现实例,并且在这种情况下仅由其成员名称引用。

    StringCollection 遵守所需的 IList 行为 - IList 不保证可以将任何任意对象添加到其中。 StringCollection 提供了更强有力的保证——主要是它只包含字符串。该类包括其自己的强类型方法,用于Add、Contains、Item 和其他用于标准用例的方法,其中它作为StringCollection 而不是IList 访问。但它仍然像IList 一样完美运行,接受和返回对象,但如果尝试添加不是字符串的项目,则会返回错误代码(如 IList 允许的那样)。

    最终,接口是否出现在类中(即显式实现)由类作者决定。对于框架类,显式实现包含在 MSDN 文档中,但不能作为类成员访问(例如,显示在自动完成上下文中)。

    【讨论】:

    • 所以我应该记住,根据经验,接口的显式实现不会出现在 .NET 库的内置类定义中? (因为我显然在 StringCollection 类的所有方法中都看不到这个实现)有什么原因吗?
    • 通常如此,实际上类似于成员阴影——我修改了响应以解决这个问题。
    【解决方案2】:

    如果您使用的是 .net 2.0+,我只会使用泛型:

    IList<string> list = new List<string>();
    

    这应该会给你你想要的一切。

    【讨论】:

      【解决方案3】:

      IList.Add(object) 可以接受字符串以外的参数——它可以接受任何类型。所以如果你声明你的接口实现只接受字符串,它就不再符合接口规范,因为现在我不能传入 Stream 例子。

      Variance 可以以 other 方式工作:如果接口方法被声明为接受字符串,那么接受对象就可以了,因为字符串也是对象,因此对接口方法的任何输入也可以成为您的实施可接受的输入。 (但是,您仍然必须提供带有接受字符串的方法的显式接口实现,因为在 C# 中,接口方法实现与接口方法声明完全匹配。)

      【讨论】:

        【解决方案4】:

        IList 基本上指定的是您可以调用Add 并将任何对象作为参数传递,从一个装箱的Int 到另一个IList 到System.DivideByZeroException。如果你只提供一个Add( string ) 方法,你就没有满足这个要求,因为你只能添加字符串。

        换句话说,您将无法调用StringCollection.Add( new Object() );,如果接口实现正确,这应该是完全可行的。 :D

        【讨论】:

          猜你喜欢
          • 2021-09-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多