使用泛型时,什么是多态性?请帮助我理解C#中的多态性。

9

我在使用泛型时,对于多态的工作原理感到困惑。例如,我定义了以下程序:

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
@Greg:好的一面是,由于这类问题不断出现,我感觉自己对这些概念的理解已经得到了充分的加强! - Dan Tao
这些概念很好,但名称有点吓人。 :) - Greg
1
@Greg:同意。我建议我们将它们更改为gobbling和spewing - Dan Tao
3个回答

4
注意:在所有情况下,您都需要将Contents字段初始化为实现IList<?>接口的具体对象。
当您保留泛型约束时,可以执行以下操作:
public IList<T> Contents = new List<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);

方法二:

或者将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();

方法三:

编辑:另一种选择是从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();
    }
}

以下是您需要翻译的内容:

以下是为什么您尝试的方法不可能(摘自我最近发布的回答):

考虑类型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));

上面的代码说明了什么是(以及不是)协变类型。请注意,如果T是协变的,在.NET 4.0中,将类型T强制转换为另一种类型T是可能的,其中D是从B派生出来的; 如果其通用类型参数仅以输出形式出现,则通用类型是协变的 - 即只读属性和函数返回值。
可以这样想:如果某些类型T始终提供B,则始终提供D(T)的类型将能够作为T操作,因为所有D都是B。
顺便说一下,如果通用类型参数仅以输入的形式出现 —— 即方法参数,则该类型是逆变的。如果类型T是逆变的,则它可以转换为T,尽管这看起来很奇怪。
可以这样想:如果某些类型T始终需要B,则它可以代替始终需要D的类型,因为所有D都是B。
您的MyContainer类既不是协变也不是逆变,因为它的类型参数在两个上下文中都出现 - 作为输入(通过Contents.Add)和作为输出(通过Contents属性本身)。

1

这是协方差的问题

http://msdn.microsoft.com/en-us/library/dd799517.aspx

你不能将 MyContainer<MyClass> 强制转换为 MyContainer<IMyInterface>,因为这样你就可以做一些像 Contents.Add(new AnotherClassThatImplementsIMyInterface()) 的事情了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接