实现接口时的C#协方差和逆变

时间:2011-08-14 01:53:23

标签: c# .net inheritance interface covariance

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

在.NET v1.0中使用了

StringCollection来为字符串创建强类型集合,而不是基于object的{​​{1}}(后来通过包含泛型集合来增强):

快速浏览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);

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

所以,为了确保,我尝试编写自己的类来实现int Add(string value); 并更改

IList

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

int Add(object value);

任何想法是什么导致了这个?

谢谢!

4 个答案:

答案 0 :(得分:8)

行为是由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行为--Ilinist不保证可以向其添加任何任意对象。 StringCollection提供更强的保证 - 主要是它只包含字符串。该类包含其自己的AddContainsItem的强类型方法,以及标准用例的其他方法,它以StringCollection而不是{IList进行访问{1}}。但它仍然可以很好地作为IList,接受和返回对象,但如果尝试添加不是字符串的项目,则返回错误代码(如IList允许)。

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

答案 1 :(得分:0)

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

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

那应该能给你你想要的一切。

答案 2 :(得分:0)

IList.Add(object)可以接受除字符串以外的参数 - 它可以接受任何类型。因此,如果您声明接口的实现仅接受字符串,则它不再与接口规范匹配,因为现在我无法传递Stream

Variance可以使用其他方式:如果接口方法被声明为接受字符串,那么接受对象就没有问题,因为字符串也是对象,因此接口方法的任何输入也会是您的实施的可接受的输入。 (但是,您仍然需要使用接受字符串的方法提供显式接口实现,因为在C#中,接口方法实现与接口方法声明完全匹配。)

答案 3 :(得分:0)

IList基本上指定的是,您可以调用Add并将任何对象作为参数传递,从框Int到另一个IList再到{{1} }。如果您只提供System.DivideByZeroException方法,则表示您尚未满足此要求,因为您只能添加字符串。

换句话说,你将无法调用Add( string ),如果接口正确实现,这应该是完全可行的。 :d