Action<List<Something>> contravariance limitation - c#

I'm writing a networked application where objects are requested by id and are returned via a delegate callback:
public static void requestById<ModelType>(T id, Action<ModelType> callback)
where ModelType : AbstractModel<T>
{
}
For convenience I have a method for requesting multiple objects at a time:
public static void requestByIds<ModelType>
(List<T> ids, Action<List<ModelType>> callback)
where ModelType : AbstractModel<T>, new()
{
}
Further down the line I have an abstract model object that has multiple children, and a method for requesting it's children:
public abstract void requestSections(Action<List<AbstractSection>> callback);
And then an implementation in a concrete class:
public override void requestSections(Action<List<AbstractSection>> callback)
{
Section.requestByIds<Section>(this.sectionIds, callback);
}
Suddenly I find the delegate
Action<List<AbstractSection>>
is incompatible with
Action<List<Section>>
Is this a limitation of contravariance in C#? Is there any kind of workaround so I can get my override method to work? Thanks

List is invariant, it is not covariant or contravariant.
If you use an IEnumerable<T> rather than a list, than you can rely on IEnumerable<T> being covariant with repsect to T, which makes Action<IEnumerable<T>> contravariant with respect to T.

Related

C# covariance issue with two covariant interfaces [duplicate]

This question already has an answer here:
Parameter must be input-safe error
(1 answer)
Closed 4 years ago.
I have two interfaces that are both covariant, with both being passed in to each other like so:
public interface Perfomer<in T>
{
void Perform(T t, Tracer<T> tracer);
}
public interface Tracer<in T>
{
void Notify();
}
However even though both interfaces are marked covariant, and T is only ever being used as input, I'm still getting the error:
"Invalid variance: The type parameter 'T' must be covariantly valid on
'Perfomer<T>.Do(T, Tracer<T>)'. 'T' is contravariant. [_Console].
Any ideas why having covariant interface parameter using the same type makes T contravariant?
Edit
(Sorry, I am new to StackOverflow, based on the answers I realize I should've been more exact in my question, I had just tried to eliminate as much noise as possible to a single error).
The code actually has two interfaces with generally similar interfaces:
public interface Performer<in T>
{
bool Perform(T t, Tracer<T> tracer = null);
}
public interface Tracer<in T>
{
void Notify(Performer<T> performer, T t, ref bool success);
}
It's purpose is to allow the an optional "tracer" to see things happen/modify the results of a performer.
When you declare that Performer is contravariant, you are declaring that anything a Performer does to a T can also be done to a more specific version of T. For example, an action that acts on a object can be given a string, and it'll just act as if that string is an object.
So for example you could do this, because all streams support Length:
class MyClass : Performer<Stream>
{
void Perform(Stream t)
{
Console.WriteLine(t.Length)
}
}
Performer<FileStream> p = new MyClass();
p.Perform(new FileStream());
But you can't do this, because you gave it a class that doesn't support IsAsync:
class MyClass : Performer<FileStream>
{
void Perform(Stream t)
{
Console.WriteLine(t.IsAsync)
}
}
Performer<Stream> p = new MyClass();
p.Perform(new Stream()); //Stream isn't good enough; it has to be a FileStream, since it needs IsAsync
So far so good. Now let's add in that second parameter:
class MyClass : Performer<Stream>
{
void Perform(Stream t, Tracer<Stream> tracer)
{
Console.WriteLine(tracer.Notify())
}
}
In order for this to work, the contravariance has to work. If the contravariance works, it means that Perform can store a Tracer<FileStream> (which you pass in) in a variable that is typed as a Tracer<Stream> (which is how it is implemented). That means that Tracer must be covariant with respect to its type argument.
So you can fix your code by changing in to out, like so:
public interface Performer<in T>
{
void Perform(T t, Tracer<T> tracer);
}
public interface Tracer<out T> //out instead of in
{
void Notify();
}
From what you've provided I'd avoid the issue all together, Modify the Tracer interface to remove the T because it's not needed:
public interface INotify
{
void Notify();
}
Then just take in an the new interface in your performer
public interface Perfomer<in T>
{
void Perform(T t, INotify entity);
}
PS: there might be a type in your interface name Perfomer => Performer
Just modifyTracer<in T> to Tracer (non-generic) and define void Perform(T t, Tracer tracer);.
Your code was not using T in Tracer anyways.
Since you edited your question with new details, the alternative fix is to remove in from generics definition. You don't need it. Another way to achieve what you want is following:
public interface Performer<T>
{
bool Perform(T t, Tracer tracer = null);
}
public interface Tracer
{
bool Notify<T>(Performer<T> performer);
}
Note: drop ref bool and return bool instead

Why the interface IOrderedEnumerable<T> isn't covariant in T?

I was Looking at the declaration of IOrderedEnumerable an I was suprised that it isn't covariant in it's TElement type parameter .
public interface IOrderedEnumerable<TElement> : IEnumerable<TElement>, IEnumerable
{
IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);
}
What's the reason for which it was not made covariant ?
It's an oversight and that was fixed in .NET Core. Here is (closed) issue about that and here is pull request which fixes it.
It's not getting fixed in full .NET version I think because that's a breaking change. For example (idea is taken from this answer which is about another breaking change, but applies here too):
public class Base
{
public void DoSomething(IOrderedEnumerable<string> strings)
{
Console.WriteLine("Base");
}
}
public class Derived : Base
{
public void DoSomething(IOrderedEnumerable<object> objects)
{
Console.WriteLine("Derived");
}
}
Then you call
Derived d = new Derived();
d.DoSomething(new List<string>().OrderBy(c => c));
If IOrderedEnumerable is not covariant - Base method would be called. Now suppose we change that to covariant. When we next time compile this code, suddenly Derived method is called.

Parameter must be input-safe error

Here is a piece of my code:
public interface IA<in TInput>
{
void Method(IB<TInput> entities);
}
public interface IB<in T> { }
I can't figure out why I get following compile error:
"Parameter must be input-safe. Invalid variance: The type parameter |TInput| must be contravariantly valid on "IB< in T>".
Any help will be appreciated.
The designator of contravariance in C# (i.e. in) is intuitive only at the immediate level, when you make a method that "takes in" a parameter of generic type. Internally, however, contravariance means an inversion of a relation (Q&A with an explanation) so using in inside IA makes it incompatible with IB.
The problem is best illustrated with an example. Consider class Animal and its derived class Tiger. Let's also assume that IB<T> has a method void MethodB(T input), which is called from IA's Method:
class A_Impl<T> : IA<T> {
T data;
public void Method(IB<TInput> entities) {
entities.MethodB(data);
}
}
Declaring IA<in TInput> and IB<in TInput> means that you can do
IA<Animal> aForAnimals = new A_Impl<Animal>();
IA<Tiger> aForTigers = aForAnimals;
IA<in TInput> has a method that takes IB<TInput>, which we can call like this:
aForTigers.Method(new B_Impl<Tiger>());
This is a problem, because now A_Impl<Animal> passes an Animal to MethodB of an interface that expects a Tiger.
You would have no problem with IB<out T>, though - both with covariance and contravariance:
public interface IB<out T> {
// ^^^
}
// This works
public interface IA<in TInput> {
void Method(IB<TInput> x);
}
// This works too
public interface IC<out TInput> {
void Method(IB<TInput> x);
}

passing Collection of abstract class as parameter in function

I want to pass ICollection<AbstractClass> in function as parameter. But when I call it with Collection of concrete types Visual Studio show me error that
method has some invalid arguments
My function is :
private void GenerateId(ICollection<BaseEntity> entities)
{
foreach (BaseEntity e in entities)
{
e.Id = _baseDao.GetNextId();
}
}
My call is :
GenerateId(entity.TitleAdmRegions);
Type of AdmRegions:
public virtual ICollection<TitleAdmRegion> TitleAdmRegions { get; set; }
And AdmRegion is:
public partial class TitleAdmRegion : BaseEntity
{
//...
}
You have to do an explicit cast - in fact, there's no guarantee that a collection of T, where T inherits from U is also a collection of U. Of course, it most likely will be, but...
The relation is called covariance - the ability to use a more specific type in generic "call" instead of its ancestor. MSDN has a nice article on the topic in C# - http://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx
The type-safe way is actually quite simple using generics:
private void GenerateId<T>(ICollection<T> entities)
where T: BaseEntity
{
foreach (var e in entities)
{
e.Id = _baseDao.GetNextId();
}
}
Also, while ICollection<T> is not covariant, IEnumerable<T> is. So another simple way would be to use IEnumerable<BaseEntity> as the parameter:
private void GenerateId<T>(IEnumerable<T> entities) { ... }
The interface ICollection<T> is not covariant in T. It couldn't be because the type contains methods such as void Add(T item). We have that
a TitleAdmRegion is a BaseEntity
but without covariance, that does not imply that
an ICollection<TitleAdmRegion> is an ICollection<BaseEntity>
as you seem to think. The solution is to switch to an interface that is covariant in its type argument. You can use either IEnumerable<out T> or IReadOnlyCollection<out T>. Covariance means that an IEnumerable<TitleAdmRegion> is an IEnumerable<BaseEntity>, and an IReadOnlyCollection<TitleAdmRegion> is an IReadOnlyCollection<BaseEntity>. So change the signature to:
private void GenerateId(IEnumerable<BaseEntity> entities) // or IReadOnlyCollection<BaseEntity>, or IReadOnlyList<BaseEntity>, etc.
{
foreach (BaseEntity e in entities)
{
e.Id = _baseDao.GetNextId();
}
}
and all will be fine.
Covariance (and contravariance) in generics was new in .NET 4.0 (2010). The interface IReadOnlyCollection<out T> was new in .NET 4.5 (2012). Note that collections that allow both reading and writing (such as the List<T> class and the T[] array type) do implement IReadOnlyCollection<out T>.

Contravariant Value Types

I have created this interface for my Repositories.
public interface IRepository<T, in TKey> where T: class
{
IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
IEnumerable<T> FindAll();
T FindSingle(TKey id);
void Create(T entity);
void Delete(T entity);
void Update(T entity);
}
The FindSingle method accepts an ID, which will be used for searching on Primary Key. By using in I expected that I would only be allowed to pass a reference type as TKey. Out of curiosity I decided to create a concrete class and specify it as an int, so I could see the exception.
I looked up MSDN and it specifies this should not work
Covariance and contravariance in generic type parameters are supported for reference types, but they are not supported for value types.
The class I created looks like this
public class ProjectRepository : IRepository<Project,int>
{
public IEnumerable<Project> Find(Expression<Func<Project, bool>> predicate)
{
throw new NotImplementedException();
}
public IEnumerable<Project> FindAll()
{
throw new NotImplementedException();
}
public Project FindSingle(int id)
{
throw new NotImplementedException();
}
public void Create(Project entity)
{
throw new NotImplementedException();
}
public void Delete(Project entity)
{
throw new NotImplementedException();
}
public void Update(Project entity)
{
throw new NotImplementedException();
}
}
Why did I not get an exception on build having specified TKey as a value type? Also, If I removed the in from my parameter what have I lost? the MSDN document says that the contravariance allows using a less derived type, but surely by removing in I can pass any type in as it is still generic.
This is maybe displaying a lack of understanding on contravariance and covariance but it has me a little confused.
Covariance and contravariance don't make as much sense on value types, because they are all sealed. Though it's not clear from the documentation, it is valid to use a struct as a co/contravariant type, it's just not always useful. The documentation you reference is most likely referring to that the following is not valid:
public struct MyStruct<in T>
Contravariance means that you can do something like the following example:
IRepository<string, Base> b = //something
IRepository<string, Derived> d = b;
Since there's nothing that derives from int, you can use an IRepository<string, int>, but only as an IRepository<string, int>.
Covariance means that you can do the reverse, e.g. IEnumerable<T> is out T, which is covariant. You can do the following:
IEnumerable<Derived> d = //something
IEnumerable<Base> b = d;
If you're trying to restrict both TKey and T to classes (reference types), you should include a second restriction:
public interface IRepository<T, in TKey>
where T : class
where TKey : class
Indeed, you are missing the whole point of co- and contravariance :-) It is about being able to assign a variable of a generic type to another variable of the same generic type but with differing generic type argument(s) that are related to the ones used in the source.
Depending on whether the generic type parameter is co- or contravariant, different assignments are allowed.
Assume the following interface:
public interface IRepository<in T>
{
void Save(T value);
}
Additionally, assume the following interface along with a value type and a reference type that implement it:
public interface IBar
{
}
public struct BarValueType : IBar
{
}
public class BarReferenceType : IBar
{
}
Finally, assume two variables:
IRepository<BarReferenceType> referenceTypeRepository;
IRepository<BarValueType> valueTypeRepository;
Contravariance now means that you can assign an instance of IRepository<IBar> to the variable referenceTypeRepository, because BarReferenceType implements IBar.
The section from the MSDN you quote simply means that the assignment of an instance of IRepository<IBar> to valueTypeRepository is not legal, although BarValueType also implements IBar.
There is no problem in implementing your interface with a value type. You will only get an error when trying to assign an IRepository<Project, object> to a IRepository<Project, int>, for example. In the following code, the last assignment won't compile:
public interface IContravariant<T, in TKey> where T : class
{
T FindSingle(TKey id);
}
public class objCV : IContravariant<Project, object>
{
public Project FindSingle(object id)
{
return null;
}
public static void test()
{
objCV objcv = new objCV();
IContravariant<Project, Project> projcv;
IContravariant<Project, int> intcv;
projcv = objcv;
intcv = objcv;
}
}
In this article, they are telling us that the type parameter is treated as invariant by the compiler:
Variance applies only to reference types; if you specify a value type
for a variant type parameter, that type parameter is invariant for the
resulting constructed type.
From: http://msdn.microsoft.com/en-us/library/dd799517.aspx

Categories

Resources