c#实现接口时的协方差和逆变性
本文关键字:方差 实现 接口 | 更新日期: 2023-09-27 18:05:36
我最近决定刷新我关于c#基础知识的记忆,所以这可能是微不足道的,但我遇到了以下问题:
在。net v1.0中使用 StringCollection
是为了创建字符串的强类型集合,而不是基于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)'
你知道这是什么原因吗?
谢谢!
该行为是由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提供了更强的保证——主要是它将只包含字符串。该类包含自己的强类型方法,用于Add
、Contains
、Item
和其他标准用例,在这些用例中,它被作为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