2010-08-25 68 views
9

我在使用泛型时如何理解多态性是如何工作的问题。作为一个例子,我已经定义了以下程序:请帮我理解在c#中使用泛型时的多态性问题#

public interface IMyInterface 
{ 
    void MyMethod(); 
} 

public class MyClass : IMyInterface 
{ 
    public void MyMethod() 
    { 
    } 
} 

public class MyContainer<T> where T : IMyInterface 
{ 
    public IList<T> Contents; 
} 

然后我就可以做到这一点,它工作得很好:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 

我有实现MyInterface很多类。我想写一个可以接受所有MyContainer对象的方法:

public void CallAllMethodsInContainer(MyContainer<IMyInterface> container) 
{ 
    foreach (IMyInterface myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

现在,我想要调用此方法。

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

这没有奏效。当然,因为MyClass实现了IMyInterface,我应该能够投它吗?

MyContainer<IMyInterface> newContainer = (MyContainer<IMyInterface>)container; 

这也没有效果。我可以肯定地蒙上了正常MyClass的到IMyInterface的:

MyClass newClass = new MyClass(); 
IMyInterface myInterface = (IMyInterface)newClass; 

所以,至少我还没有完全误解了。我不确定我是如何编写一个接受符合相同接口的类的通用集合的方法。

我有一个计划,要彻底解决这个问题,如果需要的话,但我真的更喜欢正确地做。

预先感谢您。

+2

这就是人们跑出像协变和逆变这样的恐怖词语的地方。 – Greg 2010-08-25 12:27:08

+0

@Greg:从好的方面来说,我觉得自己对这些概念的理解最近已经真正充实了,因为这样的问题已经出现过多! – 2010-08-25 12:28:20

+0

这个概念很好,但名字很可怕。 :) – Greg 2010-08-25 12:59:01

回答

4

注:在任何情况下,你必须将Contents场初始化当你把通用约束实现IList<?>

一个具体的对象,你可以这样做:

public IList<T> Contents = new List<T>(); 

当你不“T,你可以这样做:

public IList<MyInterface> Contents = new List<MyInterface>(); 

方法1:

变化的方法进行:

public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface 
{ 
    foreach (T myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

和代码段:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

方法2:

或者,将CallAllMethodsInContainer方法将MyContainer<T>类这样的:

public void CallAllMyMethodsInContents() 
    { 
     foreach (T myClass in Contents) 
     { 
      myClass.MyMethod(); 
     } 
    } 

并更改代码段:

MyContainer<MyClass> container = new MyContainer<MyClass>(); 
container.Contents.Add(new MyClass()); 
container.CallAllMyMethodsInContents(); 

方法3:

编辑:另一种替代方案是从MyContainer类中删除通用约束这样的:

public class MyContainer 
{ 
    public IList<MyInterface> Contents; 
} 

并将方法签名更改为

public void CallAllMethodsInContainer(MyContainer container) 

然后片断应该工作:

MyContainer container = new MyContainer(); 
container.Contents.Add(new MyClass()); 
this.CallAllMethodsInContainer(container); 

请注意,这个替代方案,容器的Contents名单将接受实施MyInterface对象的任意组合。

3

哇,这个问题一直在最近来了很多。

简答:不,这是不可能的。下面是可能:

public void CallAllMethodsInContainer<T>(MyContainer<T> container) where T : IMyInterface 
{ 
    foreach (IMyInterface myClass in container.Contents) 
    { 
     myClass.MyMethod(); 
    } 
} 

这里的原因是什么,你试过可能性(this recent answer of mine拍摄):

考虑List<T>类型。假设你有一个List<string>和一个List<object>。字符串来自对象,但并不遵循List<string>派生自List<object>;如果没有,那么你可以有这样的代码:

var strings = new List<string>(); 

// If this cast were possible... 
var objects = (List<object>)strings; 

// ...crap! then you could add a DateTime to a List<string>! 
objects.Add(new DateTime(2010, 8, 23));23)); 

上面的代码说明了什么它的意思是(而不是定)一covariant type。需要注意的是铸造型T<D>到另一种类型,其中T<B>D派生B是可能的(在.NET 4.0)如果T协变;如果泛型类型参数只以输出的形式出现 - 即只读属性和函数返回值,泛型类型是协变的。

认为它是这样的:如果某种类型T<B>总是供应B,然后一个总供应DT<D>)就能为T<B>因为所有D s为B s到操作。

顺便说一句,如果一个类型的泛型类型参数只以输入的形式出现 - 即方法参数,则其类型为逆变型。如果一个T<B>类型是逆变的,那么它可以投射到T<D>,看起来很奇怪。

认为它是这样的:如果某种类型T<B>总是需要B,那么就可以介入了一个总是需要D以来,再次,所有D s为B秒。

MyContainer类既不是协变也不逆变因为它的类型参数出现在两种情况下 - 输入(经由Contents.Add)和作为输出(通过Contents属性本身)。