Why doesn't C# do “simple” type inference on generics?

后端 未结 6 1265
谎友^
谎友^ 2021-01-03 05:45

Just curious: sure, we all know that the general case of type inference for generics is undecidable. And so C# won\'t do any kind of sub-typing at all: if Foo

相关标签:
6条回答
  • 2021-01-03 06:04

    The point is that you can't do it for all cases, so you don't do it for any. Where do you draw the line is the problem. If you don't do it for any than everyone who uses C# knows that it doesn't do that. If you do it part of the time, that is when it gets complicated. It can become a guessing game as to how your code will behave. It's all the edge cases on what is easy and what is not that become complex to the programmers and can cause errors in code.

    Here is a scenario that would absolutely cause havoc. Let's say that you can infer boo is bar in scenario A. Someone else comes and changes part of the base type and this no longer holds true. By making it either always apply, or never apply, you don't run into this situation, ever. In a complex environment, tracking down a problem like this can be an absolute nightmare, especially when you factor in this may not be catchable during the compile time (reflection, the DLR etc.). It's so much easier to write code to manually handle the conversion up front, than assume that it will work in your scenario when the possibility that sometime down the line it just won't (not counting upgrading to a new version).

    C# 4.0 does fix some of this as they allowed inference on what they felt is "safe" for the programmers.

    0 讨论(0)
  • 2021-01-03 06:16

    They already have solved it for many of the "easy" cases: C# 4.0 supports covariance and contravariance for generic type parameters in interfaces and delegates. But not classes unfortunately.

    It's fairly easy to workaround this limitation:

    List<Foo> foos = bars.Select(bar => (Foo)bar).ToList();
    
    0 讨论(0)
  • 2021-01-03 06:16

    it is OBVIOUS that Foo<int> is a subtype of Foo<T>

    To you maybe, but not to me.

    To me, the huge hole this rips into the type system is simply not acceptable. If you want to throw type-safety out the window like that, I'd much rather use a dynamically typed language that was actually designed for this stuff.

    The fact that arrays are covariant, even though this is known to break type-safety, is bad enough, now you want to break it for everything?

    This goes to the very heart of what a type system is about. All a type system does is reject programs. And because of Rice's Theorem, those rejected program include perfectly well-typed, type-safe programs.

    That is a huge cost. Taking away expressivity, preventing me from writing useful programs. In order to justify that cost, the type system better pay be back big time. It has basically two ways of doing that: giving back expressivity at the type-level it took away at the program-level and type-safety.

    The former is out, simply because C#'s type system isn't powerful enough to let me express anything even remotely interesting. This leaves only the latter, and it is already on pretty shaky ground because of null, covariant arrays, unrestricted side-effects, unsafe and so on. By making generic types automatically covariant, you more or less completely take away the last shred of type-safety that is left.

    There are only very few cases where S <: T ⇒ G<S> <: G<T> is actually type-safe. (IEnumerable is one such example.) And there are probably equally many cases where only S <: T ⇒ G<T> <: G<S> is type-safe. (IObservable, IComparer, IComparable, IEqualityComparer.) Generally, neither G<S> <: G<T> nor G<T> <: G<S> are type-safe.

    0 讨论(0)
  • 2021-01-03 06:21

    So, I found an elegant work-around for my real problem (although I do find that C# is overly constraining).

    As you've gathered, my main goal is to write code that can register a callback to a method that you'll write later (in 2011, for example) and that was defined externally with generic type parameters that don't exist today, hence aren't available to me at the time I wrote my library (today).

    For example I need to make a list of objects you'll define next year, and iterate over the elements of the list, calling back to each object's "Aggregate<KeyType, ValueType>" method. My frustration was that while you can certainly register your class, or an object in your class, C# type checking wasn't letting me call the methods because I don't know the types (I can get the types via GetType() but can't use those as types in expressions). So I was starting to use reflection to hand-compile the desired behavior, which is ugly and can violate type safety.

    And the answer is... anonymous classes. Since an anonymous class is like a closure, I can define my callbacks inside the "register" routine (which has access to the types: you can pass them in when you register your objects). I'll create a little anonymous callback method, right inline, and save a delegate to it. The callback method can call your aggregator since C# considers it to "know" the parameter types. And yet my list can list methods with known type signatures at the time I compile my library, namely tomorrow.

    Very clean and C# doesn't end up driving me insane. Still, C# really doesn't try hard enough to infer subtype relationships (sorry Eric: "what should be subtype relationship"). Generics aren't macros in C. Yet C# is much too close to treating them as if they were!

    And that answers my own (real) question.

    0 讨论(0)
  • 2021-01-03 06:23

    A really good real life example of a language that is muddied and made more complex than necessary by special cases is English. In a programming language things like "i before e except after c" make the language harder to use and as such, less useful.

    0 讨论(0)
  • 2021-01-03 06:25

    Since instances of generic classes have different method signatures I don't belive it would make any sence to consider specific classes to be inherited from base classes.

    class Base<T> 
    {
     public T Method() { return default(T);}
    }
    

    If you suggest that Base and Base than both of them have "Method" with the same signature (coming from base class) and it can be called by using reference to base class. Can you please explain what obvious return value "Method" will have in your system if one points to Base<int> or Base<string> object by pointing to base class?

    0 讨论(0)
提交回复
热议问题