Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does <in TFrom, out TTo> mean?

Resharper has suggested to change from

interface IModelMapper<TFrom, TTo>
{
    TTo Map(TFrom input);
}

into

interface IModelMapper<in TFrom, out TTo>

So I investigate a little and ended reading this article (found through a Wikipedia article) and some more Google.

I am still not sure what this would imply for my application so I am tempted of not accepting the suggestion. What would are the benefits this change would introduce and I am not considering by ignoring the suggestion?

More explicitly, why should I accept it?

like image 484
mhttk Avatar asked Nov 29 '11 20:11

mhttk


2 Answers

Bottom Line: Resharper has investigated your type, and discovered that TFrom may be used contravariantly, and TTo covariantly. Accepting the refactor would allow you to use these types with greater flexibility, as described below. If that might be of value to you, accept it.

Note, however, that accepting this refactor would place restrictions on how you use these types in the future. If you ever write a method that takes TTo as a parameter, you'll get a compiler error, since coviariant types cannot be read in. And ditto for TFrom: you'll never be able to have a method that returns this type, or has an out parameter of this type.


That's telling you that TFrom is contravariant, and that TTo is covariant. These were features recently added to C#

Type covariance means that a more specific type may be passed in, while contravariance means that a less specific type may be passed in.

IEnumerable<T> is a good example of type covariance. Since items in an IEnumerable<T> are read only, you may set it to something more specific:

IEnumerable<object> objects = new List<string>();

Consider what could happen if (hypothetically) you were allowed to do this for collections that were read/write:

List<object> objects = new List<string>();
objects.Add(new Car());
//runtime exception

To be type covariant, a generic parameter must be used in a strictly read-only manner; it must only ever be written out from the type, and never read in (hence the keywords). That's why the IEnumerable<T> example works, but the List<T> example doesn't. By the way, arrays do support type covariance (since Java does, I believe), and so this same kind of runtime error is possible with arrays.

Type contravariance means the opposite. To support type contravariance a generic parameter must be read in only, and never written out. This allows you to substitute less specific types in.

Action<T> is an example of type contravaince:

Action<object> objAction = (o => Console.WriteLine(o.ToString()));
Action<string> strAction = objAction;
strAction("Hello");

strAction is declared to take a string parameter, but it works fine if you substitute an object type. A string will be passed in, but if the delegate it's set to work with chooses to treat it as an object, then so be it. No harm done.

For completeness, Func<T> is the inverse case of Action<T>; here T is only returned, therefore it's covariant:

Func<string> strDelegate =  () => "Hello";
Func<object> myObjFunc = strDelegate;
object O = myObjFunc();

myObjectFunc is coded to return an object. If you set it to something that returns a string, then, again, no harm done.

like image 65
Adam Rackis Avatar answered Oct 31 '22 13:10

Adam Rackis


As an example of how this choice might affect your application, assume you have a type CustomerAddressMapper that implements IModelMapper<Customer, Address[]> and another one SupplierAddressMapper that implements IModelMapper<Supplier, Address[]>. The Customer and Supplier types share a base class Company, but their address logic is distinct, so we need separate types to handle this.

Now assume that you have a method that takes an IMapper<Company, Address[]>. Before interface contravariance, you would not be able to pass instances of CustomerAddressMapper or SupplierAddressMapper to this method. Now, if you use the in modifier on the TFrom type parameter, you can.

Furthermore, because of covariance on the TTo parameter, you can also pass a CustomerAddressMapper to methods that require an IMapper<Customer, object>.

like image 23
phoog Avatar answered Oct 31 '22 11:10

phoog