18

我正在阅读 Coders at Work 中对 Joshua Bloch 的采访,他对 Java 5 中泛型的引入感到遗憾。他不喜欢具体的实现,主要是因为对差异的支持——Java 的通配符——使它变得不必要地复杂。

据我所知,C# 3 没有任何明确的、有界通配符,例如,您不能声明一个方法 PriceBatch 采用 Asset 集合或任何 Asset 子类(void PriceBatch(Collection<? extends Asset> assets)在 Java 中?)。

有谁知道为什么没有将通配符和边界添加到 C# 中?这些特性是为了让语言更简单而故意遗漏的,还是他们还没有实现?

编辑:神圣的烟雾,来自 Eric Lippert 本人的评论!在阅读了他和 Paul 富有洞察力的评论后,我意识到至少支持上限,并且上面的示例可以转换为 C#:

void PriceBatch<T>(ICollection<T> assets) where T : Asset

另一方面,正如 Eric 在他的第二条评论中所说,显然不支持下限,例如,可能没有办法将这个(有点做作的)Java 代码直接翻译成 C#:

public class Asset {}
public class Derivative extends Asset {}
public class VanillaOption extends Derivative {}

public static <T extends Asset> void copyAssets(Collection<T> src, Collection<? super T> dst) {
    for(T asset : src) dst.add(asset);
}

Collection<VanillaOption> src = new ArrayList<VanillaOption>();
[...]
Collection<Derivative> dst = new ArrayList<Derivative>();
[...]
copyAssets(src, dst);

我对么?如果是这种情况,C# 有上限但没有下限是否有特殊原因?

4

3 回答 3

24

一个复杂的问题。

首先让我们考虑您的基本问题,“为什么这在 C# 中是非法的?”

class C<T> where T : Mammal {} // legal
class D<T> where Giraffe : T {} // illegal

也就是说,泛型类型约束可以说“T 必须是可以分配给哺乳动物类型变量的任何引用类型”,但不能说“T 必须是任何引用类型,其中的变量可以分配给 Giraffe”。为什么有区别?

我不知道。那是在我加入 C# 团队之前很久。简单的答案是“因为 CLR 不支持它”,但是设计 C# 泛型的团队与设计 CLR 泛型的团队是同一团队,所以这真的不是什么解释。

我的猜测很简单,与往常一样,必须设计、实施、测试、记录和交付给客户的功能。没有人为此功能做过任何这些事情,因此它不在语言中。我没有看到提议的功能有很大的、令人信服的好处。没有引人注目的好处的复杂功能往往会在这里被削减。

然而,这是一个猜测。下次我碰巧和从事仿制药工作的人聊天时——他们住在英格兰,所以不幸的是,他们不像是在我身边——我会问的。

至于你的具体例子,我认为保罗是正确的。您不需要下限约束就可以在 C# 中使用它。你可以说:

void Copy<T, U>(Collection<T> src, Collection<U> dst) where T : U 
{ 
    foreach(T item in src) dst.Add(item);
}

也就是说,将约束放在 T 上,而不是 U 上。

于 2010-01-03T17:57:31.573 回答
8

C# 4 引入了允许泛型中的协变和逆变的新特性。

还有其他 SO 帖子更详细地讨论了这一点: How is Generic Covariance & Contra-variance Implemented in C# 4.0?

新功能不会在所有类型中自动启用此功能,但有一种新语法允许开发人员指定泛型参数是协变的还是逆变的。

C# 4 之前的 C# 版本具有与此类似的有限功能,因为它与委托和某些数组类型有关。对于委托,允许接受基本参数类型的委托。关于数组类型,我认为它是有效的,除非涉及到装箱。也就是说,客户数组可以是对象数组的大小写。但是,整数数组不能转换为对象数组。

于 2010-01-03T00:46:13.890 回答
7

.net 已经具有等效的通配符,更具逻辑性的泛型类型约束,您可以毫无问题地做您描述的事情

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
         List<a> a = new List<a>();
         List<b> b = new List<b>();
         List<c> c = new List<c>();
         test(a);
         test(b);
         test(c);

        }

        static void test<T>(List<T> a) where T : a
        {
            return;
        }
    }
    class a
    {

    }
    class b : a
    {

    }
    class c : b
    {

    }
}

示例 2

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            ICollection<VanillaOption> src = new List<VanillaOption>();
        ICollection<Derivative> dst = new List<Derivative>();
         copyAssets(src, dst);
        }

        public static void copyAssets<T,G>(ICollection<T> src, ICollection<G> dst) where T : G {
            foreach(T asset in src) 
                dst.Add(asset);
        }
    }
    public class Asset {}
    public class Derivative : Asset {}
    public class VanillaOption : Derivative {}
}

此示例表示您在 java 中的示例的代码转换。

不过,我真的无法回答实际问题!

于 2010-01-03T00:57:03.760 回答