4

http://nullprogram.com/blog/2014/04/01/ tries to explain that Java's generics can't simulate duck typing with an example:

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.

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

C#'s generics is implemented in terms of reification instead of type erasure. Does C#'s generics not have the above limitation as Java's generics? So can C#'s generics actually achieve the same thing as duck typing?

Thanks.

Tim
  • 1
  • 141
  • 372
  • 590
  • (Not really helpful, but as a piece of trivia the C# compiler implements `foreach` with duck typing - you can foreach anything that has the required methods, no interface requirement.) – AnorZaken Jan 14 '22 at 09:58

3 Answers3

4

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

No. But C#'s generics can include a constraint where the type parameter is restricted to inherit or implement some particular type. When that's done, any expression of the type of that type parameter is resolved as the constrained type and members from that type can be accessed.

This is similar to the extends constraint described in the article you read.

The only duck-typing support in C# is the dynamic keyword, where final compilation of expressions involving dynamic values is deferred until runtime when the actual runtime type is known.

Related reading:

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

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thanks. "C#'s generics **can** include a constraint where the type parameter is restricted to inherit or implement some particular type." Is C# generics allowed to not have such a constraint? If yes, doesn't C# generics without such a constraint achieve the same thing as duck typing? – Tim Sep 18 '17 at 02:46
  • 1
    _"doesn't C# generics without such a constraint achieve the same thing as duck typing"_ -- no. Without a constraint (which is allowed...the type parameter can be completely open if you like), the type is implicitly constrained to `System.Object` and the code using that generic type can only use members from `System.Object` (i.e. `Equals()` and `GetHashCode()`). – Peter Duniho Sep 18 '17 at 02:48
  • Why "Without a constraint (which is allowed...the type parameter can be completely open if you like), the type is implicitly constrained to `System.Object`"? Do you mean that it is not possible that "the type parameter can be completely open if you like"? – Tim Sep 18 '17 at 02:49
  • By the way, IMHO the referenced blog author is conflating C++ templates with duck typing incorrectly. I mean, yes...templates in C++ have a similar effect. But there is still compile-time type safety; the template is applied at compile time and the type of objects used in the template have to be known at that time. It looks a bit like duck typing, but IMHO "duck typing" is more properly applied to behavior in dynamically typed languages. – Peter Duniho Sep 18 '17 at 02:51
  • _"Do you mean that it is not possible that "the type parameter can be completely open if you like""_ -- I guess that depends on what you mean by "completely open". Every object in C# inherits `System.Object`. There is no way to write code in C# where you can't assume at least that much. To me, code that knows _only_ the `System.Object` type is code where the type is "completely open". But if you mean "have no idea what the type is at all", then in that sense, it's not literally possible for a type to be "completely open". IMHO, that's not a useful definition of "completely open". – Peter Duniho Sep 18 '17 at 02:53
2

C# has the same limitations.

Other than dynamic, C# does not have arbitrary ducky-typing anywhere; even with generics, you can only call methods as defined by a type (specifically, the constraint(s) for the generic type parameter, which default to object).

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Thanks. "even with generics, you can only call methods as defined by a type (specifically, **the constraint(s)** for the generic type parameter, which default to object." Is C# generics allowed to not have such a constraint? If yes, doesn't C# generics without such a constraint achieve the same thing as duck typing? – Tim Sep 18 '17 at 02:47
  • @Tim: If there is no constraint, you can't assume anything about the type parameter, other than `object`. – SLaks Sep 18 '17 at 14:51
2

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

Not exactly. While most generics are erased, it is possible to include a constraint in Java such that the type parameter must be of a certain type. Which effectively makes it not Object.

Untested, but this should be close.

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();
    }
}
markspace
  • 10,621
  • 3
  • 25
  • 39
  • I think what @Antonín is trying to say is, the OP (should) already be aware of the information in your answer, because it's fully addressed by example in the blog article they read and refer to at the beginning of their question. – Peter Duniho Sep 18 '17 at 03:13
  • Thanks. @Peter: (1) is it correct that the reason that Java's generics can't simulate duck typing is not because of type erasure, but, because all classes inherit `java.lang.Object` (the same reason as C#'s generics can't simulate duck typing)? (2) Why does the information `T extends CallMe` not get lost during type erasure? If `CallMe` is not an interface but a class, will the information `T extends CallMe` get lost during type erasure? – Tim Sep 18 '17 at 03:36
  • @Tim: the main reason Java and C# generics both "can't simulate duck typing" (not sure what "simulate" has to do with anything here, but whatever...) is that duck typing really has nothing at all to do with generics, and precious little to do with C++ templates for that matter (though I agree C++ templates behave a little like duck typing). The Java constraint _does_ get lost during type erasure. It's used only at compile time. At run-time, there's no way to recover the constraint. And it doesn't matter whether `CallMe` is a class or an interface; they work the same. – Peter Duniho Sep 18 '17 at 03:41
  • @Peter: do you mean that during compile time, the information `T extends CallMe` is used to verify that `callee` has a method `call()`, and then is erased during type erasure? – Tim Sep 18 '17 at 03:45
  • @Tim: yes, exactly. Usages of the generic type are required to meet the constraint at compile time. At run-time, there's no need to do the type checking, so that type information is "erased". – Peter Duniho Sep 18 '17 at 03:53
  • @Peter: In Java, does `class Caller` imply that `T` is a subclass of `java.lang.Object` besides implementing interface `CallMe`? Similarly in C#, does `class Caller where T: CallMe` imply that `T` is a subclass of `System.Object` besides implementing interface `CallMe`? – Tim Sep 18 '17 at 17:07
  • @Tim: Does `extends` imply that? No. The fact that the object is an object is what implies that. _Every_ object inherits the common base class `System.Object` (C#) or `java.lang.Object` (Java). – Peter Duniho Sep 18 '17 at 17:10