Why does C # not make "simple" type inference for generics?

Just curious: of course, we all know that the general case of type inference for generics is insoluble. And so C # will not do any subtypes at all: if Foo <T> is shared, Foo <int> is not a subtype of Foo <T> or Foo <Object> or anything else you can cook. And, of course, we all trick this with an ugly interface or abstract class definitions.

But ... if you cannot defeat a common problem, why not just limit the solution to easily accessible cases. For example, in my list above, OBVIOUS that Foo <int> is a subtype of Foo <T> , and it would be trivial to check. Same thing for checking Foo <Object> .

So, there’s still some deep horror that would fly out of the abyss if they just said: “Who cares, we will do everything we can? Or is it just some kind of religious purity on the part of the guys from Microsoft?


Update:

This is a very old stream. There is a var in C # these days that solves half of what I complained about, and then using the style of anonymous Linq delegates, has an excellent notation, because you don't have to type the same stuff twice. So every aspect of what I objected to was eliminated by later changes in C # (or maybe it just took me a while to find out about the things that were just introduced when I posted the thread ...) I I use these new ones now works in Isis2 system for reliable cloud computing (isis2.codeplex.com), and I think that as a result the library has a very clean appearance. Check it out and let me know what you think). - Ken Birman (July 2014)

+4
source share
6 answers

They have already solved this for many “simple” cases: C # 4.0 supports covariance and contravariance for type-type parameters in interfaces and delegates, but, unfortunately, not classes.

To alleviate this limitation is quite easy:

List<Foo> foos = bars.Select(bar => (Foo)bar).ToList(); 
+7
source

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

Perhaps, but not for me.

For me, a huge hole that breaks into the type system is simply unacceptable. If you want to drop the security type in such a window, I would rather use a dynamically typed language that was actually intended for this material.

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

This refers to the very heart of the type system. The entire type system performs program rejections. And due to Rhys's theorem, these rejected programs include perfectly well-typed, type-safe programs.

This is a huge cost. We remove expressiveness, preventing me from writing useful programs. To justify this cost, a type system is best paid most of the time. There are basically two ways to do this: render expressiveness at the level of the level that he selected at the program level and type of security.

The first is missing, simply because a system like C # is not powerful enough to allow me to express anything even remotely interesting. This leaves only the latter, and it is already on a rather shaky basis of null , covariant arrays, unlimited side effects, unsafe , etc. If you make generic types automatically covariant, you more or less completely select the latest security type information that remains.

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 .) As a rule, neither G<S> <: G<T> nor G<T> <: G<S> are type safe.

+5
source

The fact is that you cannot do this for all cases, so do not do this for anyone. Where you draw the line is the problem. If you do not do this not for everyone who uses C #, he knows that he does not. If you do this part of the time, that is, when it becomes difficult. This can be a guessing game as to how your code will behave. These are all extreme cases on what is easy and what is not difficult for programmers and can cause errors in the code.

Here is a scenario that would completely cause chaos. Let's say you can conclude that boo is a bar in scenario A. Someone else comes in and changes a part of the base type, and this is no longer done. Putting it to always apply or never apply, you do not encounter such a situation, ever. In a complex environment, tracking such a problem can be an absolute nightmare, especially if you take this into account, it may not be captured at compile time (reflection, DLR, etc.). It is much easier to write code to manually process the transformation from the front than to assume that it will work in your script when there is a chance that someday there will simply not be one line (apart from updating to the new version).

C # 4.0 fixes some of them, as they allow the conclusion that they consider it safe for programmers.

+2
source

A really good example of the real life of a language that has become confused and more complex than necessary in special cases is English. In a programming language, things like "i before e except after c" complicate the use of the language and, as such, are less useful.

0
source

Since instances of generic classes have different method signatures, I do not believe that it would make any case consider specific classes to inherit from base classes.

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

If you assume that Base and Base than both of them have a “Method” with the same signature (coming from the base class), and you can call it using the link to the base class. Could you explain what obvious return value the Method will have on your system if you point to a Base<int> or Base<string> object, pointing to a base class?

0
source

So, I found an elegant workaround for my real problem (although I believe C # is too restrictive).

As you have gathered, my main goal is to write code that can register a method callback that you write later (for example, in 2011) and which was defined externally with parameters of a general type that do not exist today, therefore are not accessible to me at that the time when I wrote my library (today).

For example, I need to create a list of objects that you define next year, and iterate over the elements of the list, referring to each object "Aggregate <KeyType, ValueType>" method. My frustration was that although you can, of course, register your class or object in your class, checking the C # type did not allow me to call methods because I don't know the types (I can get the types via GetType (), but can't use them as types in expressions). So I started using reflection to compile the desired behavior, which is ugly and could 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 types: you can pass them when registering your objects). I will create a small anonymous callback method right in the line and save it. Your aggregator can call the callback method, because C # considers it to be "knowledge" of parameter types. And yet, my list can display methods with known type signatures at the time of compilation of my library, namely tomorrow.

Very clean and C # doesn't end up driving me crazy. However, C # doesn’t really try hard enough to derive subtype relationships (sorry Eric: “what should be the subtype relationship”). Generics are not macros in C. However, C # is too close to treat them as if they were!

And that answers my (real) question.

0
source

Source: https://habr.com/ru/post/1332959/


All Articles