Reputation: 11
C# 4.0 introduced Covariance and Contravariance, and the keywords in
and out
.
But why do we have to be explicit about it? The compiler could have known it's OK if we pass a "SMALL" type to a "BIG" type, otherwise it gives an error itself.
And I find that it works intelligently in some other languages.
Such as in Salesforce apex code, I can write this without any error:
List<object> a = new List<Integer>();
But in C# I can not do that, and you have to tell the compiler "in" or "out".
TResult Del<in T, out TResult>(T instance)
What I'm thinking is if the compiler can figure it out by itself, we can write less code.
For example.
delegate T Function<out T>();
static void Test()
{
Function<Son> funcBar = new Function<Son>(GetInstance);
Function<Person> funcFoo = funcBar; //the compiler knows I'm passing a small type to a big type, why it's necessary to tell it `out`
Person foo = funcFoo();
}
static Son GetInstance()
{
return new Son();
}
class Person
{
}
class Son : Person
{
}
Upvotes: 0
Views: 92
Reputation: 8947
This is already mentioned in the comments but here is an example of why the first example shouldn't work,
List<object> a = new List<Integer>(); // <--- assuming this works
a.Add(new Integer()); // <--- works fine
a.Add(new object()); // <--- compiler would let this through but it is a logical error
The type of a
is List<object>
therefore the last statement is valid but it cannot work as the underlying implementation is of type List<Integer>
. There is a valid scenario where
object b = new Integer()
a.Add(b) // <--- compiles and runs fine
But this dangerous cast
(i.e. could be a runtime exception) should not be implicitly allowed by the framework but specified explicitly by the programmer.
Delegates
Delegates are no different, in fact in
and out
refer to input safety and output safety in any generic context be it collections or delegates.
With the out
keyword
Function<Son> funcBar = new Function<Son>(GetInstance);
Function<Person> funcFoo = funcBar; // <--- output safety enables this
Function<Son> funcFooBar = funcFoo; // <--- output safety prevents this
The argument here is that a less specific function can always return a more specific type but a more specific function cannot return a less specific type. i.e. Son cannot return a Person because Son has more properties than a Person ever will, and trying to access Son properties on a Person object is a run-time exception unless the Person reference holds a Son object which you have to cast explicitly at your own risk.
With the in
keyword
delegate void Func<in T>;
Func<Person> funcBar = (person) => person.Name = "John";
Func<Son> funcFoo = funcBar; // <--- input safety enables this
Func<Person> funcFooBar = funcFoo; // <--- input safety prevents this
The argument here is that a less specific function can modify a more specific object safely. I.e. a function that modifies a Person can also modify a Son as the Son is also a Person. But a function that modifies a Son cannot modify a Person because the Son has more characteristics (properties/members/fields) than a Person ever will and trying to modify a Son's properties on a Person object is impossible.
The anwser
Knowing this behaviour, the compiler cannot infer this information because the compiler cannot assume anything about the concrete implementation of the generic type. In fact, the compiler may not even see any implementation of the generic type at compile time, for eg: when building an external library.
What you have done here is looked at the concrete implementation of the generic and concluded the that the out
parameter is not needed in the first place. Generics and other abstractions are a contract on how concrete implementations should behave in concrete types. The contract occurs first and the compiler uses this contract to judge implementations and make sure they are up to specification not the other way round.
Upvotes: 2