我理解,如果S
是T
的子类,那么List<S>
不是List<T>
的孩子。精细。但是接口有不同的范例:如果Foo
实现IFoo
,那么为什么List<Foo>
不是List<IFoo>
的一个例子?
由于可能没有实际的类IFoo
,这是否意味着在展示List<IFoo>
时我总是必须强制转换列表中的每个元素?或者这只是糟糕的设计,我必须定义我自己的集合类ListOfIFoos
才能使用它们?对我来说似乎都不合理......
考虑到我正在尝试编程接口,那么暴露这样一个列表的最佳方式是什么?我目前正倾向于将List<Foo>
内部存储为List<IFoo>
。
答案 0 :(得分:14)
List<Foo>
List<IFoo>
不是MyOwnFoo
的子类,因为您无法在其中存储IFoo
对象,这也恰好是List<IFoo>
实现。 (Liskov substitution principle)
存储List<Foo>
而不是专用{{1}}的想法是可以的。如果您需要将列表的内容强制转换为它的实现类型,这可能意味着您的界面不合适。
答案 1 :(得分:12)
以下是您无法做到的原因示例:
// Suppose we could do this...
public List<IDisposable> GetDisposables()
{
return new List<MemoryStream>();
}
// Then we could do this
List<IDisposable> disposables = GetDisposables();
disposables.Add(new Form());
此时,为了保存MemoryStreams而创建的列表现在包含一个Form。糟糕!
基本上,这种限制是为了保持类型安全。在C#4和.NET 4.0中,将有有限的支持(它被称为 variance ),但它仍然不支持这种特定情况,原因如上所述
答案 2 :(得分:8)
在返回的函数中,您必须使列表成为接口列表,并在创建对象时将其作为实现它的对象。像这样:
function List<IFoo> getList() { List<IFoo> r = new List<IFoo>(); for(int i=0;i<100;i++) r.Add(new Foo(i+15)); return r; }
答案 3 :(得分:5)
MASSIVE EDIT
您将能够使用C#4.0,但 [感谢Jon]
您可以使用ConvertAll
来解决问题:
public List<IFoo> IFoos()
{
var x = new List<Foo>(); //Foo implements IFoo
/* .. */
return x.ConvertAll<IFoo>(f => f); //thanks Marc
}
答案 4 :(得分:0)
简单的答案是List<Foo>
与List<IFoo>
的类型不同,例如DateTime
与IPAddress
不同的相同方式。
但是,您IFoo
的事实意味着IFoo
的集合预计至少包含两个IFoo
FooA
,FooB
的实现等等...)因为如果您希望只有IFoo
Foo
的一个实现,那么IFoo
类型就是多余的。
因此,如果只有一个派生类型的接口,请忘记接口并节省开销。如果有两个或多个派生类型的接口,则始终在collections / generic参数中使用接口类型。
如果你发现自己编写了thunking代码,那么某处可能存在设计缺陷。
答案 5 :(得分:0)
如果在发明IList<T>
时,Microsft已经意识到.net的未来版本将支持接口协方差和逆变,那么将接口拆分为{{1}将是可能和有用的。 },IReadableList<out T>
和IAppendable<in T>
会继承上述两种情况。这样做会对vb.net实现者施加少量额外的工作(他们必须定义索引属性的只读和读写版本,因为由于某种原因.net不允许读写要作为只读属性的属性)但是意味着只需要从列表中读取项目的方法可以以协变方式接收IList<T>
,并且只需要他们可以附加的集合的方法可以接收以逆变的方式IReadableList<T>
。
不幸的是,今天可以实现这样的事情的唯一方法是,如果微软提供了一种新接口可以替代旧接口的方法,旧接口的实现会自动使用新接口提供的默认方法。我认为这样的功能(界面可替代性)会非常有用,但我不会屏住呼吸等待微软实施它。
鉴于没有办法将IAppendable<T>
反馈到IReadableList<T>
,另一种方法是定义一个自己的列表相关接口。这样做的一个难点是IList<T>
的所有实例都必须用其他类型替换,尽管如果要在不同的类型中定义System.Collections.Generic.List<T>
结构,可以最大限度地减少这样做的难度。包含单个List<T>
字段的命名空间,并定义了与系统类型之间的扩展转换(使用结构而不是类)意味着代码可以避免在结构体的任何场景中进行转换时创建新堆对象的需要不必盒装)。