使用 Java 和 C# 的泛型来模拟鸭子类型

Using Java and C#'s generics to simulate duck typing

http://nullprogram.com/blog/2014/04/01/ 试图用一个例子来解释 Java 的泛型不能模拟鸭子类型:

class Caller<T> {
    final T callee;
    Caller(T callee) {
        this.callee = callee;
    }
    public void go() {
        callee.call();  // compiler error: cannot find symbol call
    }
}

class Foo {
    public void call() { System.out.print("Foo"); }
}

class Bar {
    public void call() { System.out.print("Bar"); }
}

public class Main {
    public static void main(String args[]) {
        Caller<Foo> f = new Caller<>(new Foo());
        Caller<Bar> b = new Caller<>(new Bar());
        f.go();
        b.go();
        System.out.println();
    }
}

The program will fail with a compile-time error. This is the result of type erasure. Unlike C++’s templates, there will only ever be one compiled version of Caller, and T will become Object. Since Object has no call() method, compilation fails.

是否意味着通过Java泛型,类型参数的方法仅限于classjava.lang.Object的方法?

C# 的泛型是根据具体化而不是类型擦除来实现的。 C#的泛型没有Java的泛型有上述限制吗?那么 C# 的泛型真的可以实现与 duck typing 相同的功能吗?

谢谢。

C# 具有相同的限制。

除了 dynamic,C# 在任何地方都没有任意的 ducky-typing;即使使用泛型,您也只能调用由类型定义的方法(具体来说,泛型类型参数的约束,默认为 object)。

can C#'s generics actually achieve the same thing as duck typing?

没有。但是 C# 的泛型可以包含一个约束,其中类型参数被限制为继承或实现某些特定类型。完成后,该类型参数类型的任何表达式都将解析为约束类型,并且可以访问该类型的成员。

这类似于您阅读的文章中描述的 extends 约束。

C# 中唯一的鸭子类型支持是 dynamic 关键字,其中涉及 dynamic 值的表达式的最终编译被推迟到实际运行时类型已知的运行时。

相关阅读:

Trivial C# class with a generic parameter wouldn't compile for no apparent reason
Call a method of type parameter

Does it mean that by Java generics, the methods of a type parameter are limited to the methods of class java.lang.Object?

不完全是。虽然大多数泛型都被删除了,但可以在 Java 中包含一个约束,这样类型参数必须是特定类型。这实际上使它不是 Object.

未经测试,但这应该很接近。

class Caller<T extends CallMe> {
    final T callee;
    Caller(T callee) {
        this.callee = callee;
    }
    public void go() {
        callee.call();  // should work now
    }
}

interface CallMe {
    void call();
}

class Foo implements CallMe {
    public void call() { System.out.print("Foo"); }
}

class Bar implements CallMe {
    public void call() { System.out.print("Bar"); }
}

public class Main {
    public static void main(String args[]) {
        Caller<Foo> f = new Caller<>(new Foo());
        Caller<Bar> b = new Caller<>(new Bar());
        f.go();
        b.go();
        System.out.println();
    }
}