【问题标题】:Please help me understand polymorphism when using generics in c#请帮助我在 C# 中使用泛型时理解多态性
【发布时间】:2010-08-25 12:04:37
【问题描述】:

在使用泛型时,我无法理解多态性的工作原理。例如,我定义了以下程序:

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;

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

如果需要的话,我有一个彻底解决这个问题的计划,但我真的更愿意做正确的事情。

提前谢谢你。

【问题讨论】:

  • 这是人们大谈特谈协变和逆变等可怕词的地方。
  • @Greg:从好的方面来说,我觉得我对这些概念的自己的理解最近确实得到了充实,因为这样的问题太多了起来!
  • 概念很好,但名字很吓人。 :)
  • @Greg:同意。我建议我们将它们更改为gobbling and spewing

标签: c# asp.net generics asp.net-mvc-2 nested-generics


【解决方案1】:

注意:在所有情况下,您都必须将Contents 字段初始化为实现IList&lt;?&gt; 的具体对象

当你保持通用约束时,你可以这样做:

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

如果你不这样做,你可以这样做:

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

方法一:

将方法改为:

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

sn-p 到:

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

方法二:

或者,将CallAllMethodsInContainer 方法移动到MyContainer&lt;T&gt; 类,如下所示:

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

并将 sn-p 更改为:

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)

那么 sn-p 应该是这样的:

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

请注意,使用此替代方法,容器的 Contents 列表将接受实现 MyInterface 的任何对象组合。

【讨论】:

    【解决方案2】:

    哇,这个问题最近出现了很多。

    简短回答:不,这是不可能的。以下是可能的

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

    这就是为什么您尝试的方法不可能(取自this recent answer of mine):

    考虑List&lt;T&gt; 类型。假设您有一个List&lt;string&gt; 和一个List&lt;object&gt;。字符串派生自对象,但并不意味着List&lt;string&gt; 派生自List&lt;object&gt;;如果是这样,那么你可以有这样的代码:

    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协变,则可以将类型T&lt;D&gt; 转换为另一个类型T&lt;B&gt;,其中D 派生自B(在.NET 4.0 中);如果泛型类型参数只以输出形式出现——即只读属性和函数返回值,则泛型类型是协变的。

    这样想:如果某个类型 T&lt;B&gt; 总是提供 B,那么总是提供 D (T&lt;D&gt;) 的类型将能够作为T&lt;B&gt;,因为所有Ds 都是Bs。

    顺便说一句,如果一个类型的泛型类型参数只以输入的形式出现——即方法参数,那么它就是逆变。如果类型 T&lt;B&gt; 是逆变的,那么它可以转换为 T&lt;D&gt;,这可能看起来很奇怪。

    这样想:如果某些类型 T&lt;B&gt; 总是需要 B,那么它可以介入总是需要 D 的类型,因为同样,所有 @987654347 @s 是 Bs。

    您的MyContainer 类既不是协变也不是逆变,因为它的类型参数出现在两种上下文中——作为输入(通过Contents.Add)和作为输出(通过Contents 属性本身)。

    【讨论】:

      【解决方案3】:

      这是协方差问题

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

      您不能将MyContainer&lt;MyClass&gt; 转换为MyContainer&lt;IMyInterface&gt;,因为那样您就可以执行Contents.Add(new AnotherClassThatImplementsIMyInterface()) 之类的操作

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多