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

本文关键字:方差 实现 接口 | 更新日期: 2023-09-27 18:05:36

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

在。net v1.0中使用

StringCollection是为了创建字符串的强类型集合,而不是基于objectArrayList(后来通过包含泛型集合增强了这一点):

快速浏览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#实现接口时的协方差和逆变性

该行为是由IList.Add(object)的显式实现而不是co/逆变引起的。根据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提供了更强的保证——主要是它将只包含字符串。该类包含自己的强类型方法,用于AddContainsItem和其他标准用例,在这些用例中,它被作为StringCollection而不是IList访问。但是作为IList,它仍然可以很好地工作,接受并返回对象,但是如果试图添加一个不是字符串的项,则返回一个错误代码(如IList所允许的)。

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

如果你使用的是。net 2.0+,我会使用泛型:

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

你想要的应该都有了

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

方差可以其他方式工作:如果接口方法被声明为接受字符串,那么接受对象将是正确的,因为字符串也是对象,因此接口方法的任何输入也将是您实现的可接受输入。(但是,您仍然需要提供一个带有接受字符串的方法的显式接口实现,因为在c#中,接口方法实现与接口方法声明完全匹配。)

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

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