【问题标题】:When should or shouldn't I be using generic type constraints?什么时候应该或不应该使用泛型类型约束?
【发布时间】:2009-08-02 19:10:50
【问题描述】:

我有一个基类:

public abstract class StuffBase
{
    public abstract void DoSomething();
}

还有两个派生类

public class Stuff1 : StuffBase
{
    public void DoSomething()
    {
        Console.WriteLine("Stuff 1 did something cool!");
    }
    public Stuff1()
    {
        Console.WriteLine("New stuff 1 reporting for duty!");
    }
}

public class Stuff2 : StuffBase
{
    public void DoSomething()
    {
        Console.WriteLine("Stuff 2 did something cool!");
    }
    public Stuff1()
    {
        Console.WriteLine("New stuff 2 reporting for duty!");
    }
}

好的,现在说我有一个项目列表:

var items = new List<StuffBase>();
items.Add(new Stuff1());
items.Add(new Stuff2());

我希望他们都调用他们的 DoSomething() 方法。我可以期望只迭代列表并调用他们的 DoSomething() 方法,所以假设我有一个名为 AllDoSomething() 的方法,它只是迭代列表并完成工作:

public static void AllDoSomething(List<StuffBase> items)
{
    items.ForEach(i => i.DoSomething());
}

以下方法的实际区别是什么?

public static void AllDoSomething<T>(List<T> items) where T: StuffBase
{
    items.ForEach(i => i.DoSomething());
}

这两种方法虽然在语法上有所不同,但实际上都是在做同样的事情。

他们只是做同一件事的不同方式吗?我了解泛型和类型约束,但不明白为什么在这种情况下我会使用一种方式而不是另一种方式。

【问题讨论】:

    标签: c# generics c#-2.0 type-constraints


    【解决方案1】:

    这是因为目前 C# 还不支持Covariance

    更正式地说,在 C# v2.0 中,如果 T 是 U 的子类型,则 T[] 是 U[],但 G 不是 G 的子类型 (其中 G 是任何泛型类型)。在 类型论术语,我们描述 通过说 C# 数组来实现这种行为 类型是“协变的”和通用的 类型是“不变的”。

    参考:http://blogs.msdn.com/rmbyers/archive/2005/02/16/375079.aspx

    如果你有以下方法:

    public static void AllDoSomething(List<StuffBase> items)
    {
        items.ForEach(i => i.DoSomething());
    }
    
    var items = new List<Stuff2>();
    x.AllDoSomething(items); //Does not compile
    

    好像你使用泛型类型约束一样。

    有关协方差和逆变的更多信息],请查看Eric Lippert's series of posts


    其他值得一读的帖子:

    【讨论】:

    • 所以如果我理解正确:在我的示例中,如果没有类型约束,我无法传递 List 或 List 的实例,因此必须传递 List 的实例 而使用类型约束,我可以吗?
    • 即AllDoSomething(List items) where T : StuffBase 允许我传入 List 或 List 的实例
    • 是的,因为这样你就可以通过一个简单的通用多态约束“绕过”形式参数的协方差
    • 谢谢,这很有意义。我想这并不是我没有理解协方差本身,我只是错过了重点。我现在明白了。
    【解决方案2】:

    假设你有一个列表:

    List<Stuff1> l = // get from somewhere
    

    现在试试:

    AllDoSomething(l);
    

    对于通用版本,它是允许的。对于非泛型,它不会。这就是本质的区别。 Stuff1 的列表不是 StuffBase 的列表。但在一般情况下,您不需要它完全是StuffBase 的列表,因此更加灵活。

    您可以通过首先将Stuff1 列表复制到StuffBase 列表中来解决此问题,以使其与非通用版本兼容。但是假设你有一个方法:

    List<T> TransformList<T>(List<T> input) where T : StuffBase
    {
        List<T> output = new List<T>();
    
        foreach (T item in input)
        {
            // examine item and decide whether to discard it,
            // make new items, whatever
        }
    
        return output;
    }
    

    如果没有泛型,您可以接受StuffBase 的列表,但您必须返回StuffBase 的列表。如果调用者知道项目确实是派生类型,则调用者将不得不使用强制类型转换。因此泛型允许您保留参数的实际类型并通过方法将其引导到返回类型。

    【讨论】:

    • 所以本质区别是我的列表不能定义为我的派生类型的列表——而我的基类列表可以包含我的派生类型?
    • 请记住,List&lt;B&gt; 类型的变量不能分配有List&lt;D&gt; 类型的对象,即使B 类型的变量可以接受D。这是因为像 List 这样的类可以有像 Add 这样的方法。如果一个变量是 List&lt;D&gt;,您不应该将其静默转换为 List&lt;B&gt;,因为这将允许将 B 对象添加到其中(当它实际上是 List&lt;D&gt; 时)。
    【解决方案3】:

    在您提供的示例中没有区别,但请尝试以下操作:

    List<Stuff1> items = new List<Stuff1>();
    items.Add(new Stuff1());
    AllDoSomething(items);
    AllDoSomething<StuffBase>(items);
    

    第一个调用运行良好,但第二个调用由于泛型协方差而无法编译

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-03
      • 2010-11-17
      • 2010-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多