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?
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.
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>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With