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.
Related
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
Let's say we have an interface like
public interface IEnumerable<out T>
{ /*...*/ }
that is co-variant in T.
Then we have another interface and a class implementing it:
public interface ISomeInterface {}
public class SomeClass : ISomeInterface
{}
Now the co-variance allows us to do the following
IEnumerable<ISomeInterface> e = Enumerable.Empty<SomeClass>();
So a IEnumerable<SomeClass> is assignable to a variable (or method parameter) of type IEnumerable<ISomeInterface>.
But if we try this in a generic method:
public void GenericMethod<T>(IEnumerable<T> p) where T : ISomeInterface
{
IEnumerable<ISomeInterface> e = p;
// or
TestMethod(p);
}
public void TestMethod(IEnumerable<ISomeInterface> x) {}
we get the compiler error CS0266 telling us that an IEnumerable<T> cannot be converted to an IEnumerable<ISomeInterface>.
The constraint clearly states the T is derived from ISomeInterface, and since IEnumerable<T> is co-variant in T, this assignment should work (as shown above).
Is there any technical reason why this cannot work in a generic method? Or anything I missed that makes it too expensive for the compiler to figure it out?
Change your GenericMethod and add generic constraint class:
public void GenericMethod<T>(IEnumerable<T> p) where T : class, ISomeInterface
{
IEnumerable<ISomeInterface> e = p;
// or
TestMethod(p);
}
Covariance does not support structs, so we need to tell that we want to use classes only.
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>.
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
I don't understand why the compiler can't resolve the correct overload to use here. (code below) There is only one version of Add() that is appropriate- BigFoo is an IFoo, and does not implement IEnumerable where T is an IFoo. But it insists on reporting an ambiguity. Any ideas? I tried adding a second generic type parameter- Add where T : IFoo where U : IEnumerable. But then the overload is completely ignored even for legitimate use.
I know I can work around this with casting and specifying generic type parameters but at that point I've defeated the purpose of having an overload. You could question the overload, but the semantics feel correct to me- the behavior I'm implementing in my class is for both Add() to add the object wholesale as an individual entry in the collection. (the second Add() is not supposed to be an AddRange().)
namespace NS
{
interface IFoo { }
class BigFoo : IFoo, IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
class FooContainer
{
public void Add(IFoo item) { }
public void Add<T>(IEnumerable<T> group) where T : IFoo { }
}
class DemoClass
{
void DemoMethod()
{
BigFoo bigFoo = new BigFoo();
FooContainer fooContainer = new FooContainer();
// error CS0121: The call is ambiguous between the following methods or properties:
// 'NS.FooContainer.Add(NS.IFoo)' and
// 'NS.FooContainer.Add<int>(System.Collections.Generic.IEnumerable<int>)'
fooContainer.Add(bigFoo);
}
}
}
Generic overload resolution doesn't take constraints into account, so it deems the Add<T> version to be applicable, inferring T=int.
Both methods are applicable, and neither is definitely better than the other, as there is no conversion between IEnumerable<int> and IFoo. While generic methods are deemed "less specific" than non-generic methods, this only becomes relevant when the parameter types are identical after type argument replacement, which they're not in this case.
In FooContainer, on the second "Add" you are constraining T to be of type IFoo. BigFoo implements the IFoo interface, therefore it kinda matches that Add definition (even though it doesn't really, because it doesn't implement IEnumable<IFoo>).
I'm not sure I understand completely what you want, but I suspect it is this:
public void Add<T>(T group) where T : IEnumerable<IFoo> { }
which would allow you to add any object T where T is an enumerable set of IFoo objects.
Is that what you wanted?
Regards,
Richard
The problem here is that generic type constraints are completely ignored by the compiler (it only looks at parameter types). As far as the compiler is concerned, the IEnumerable<T> argument being passed could just as well be a IEnumerable<IFoo>.
For complete information on this subject, refer to section 25.6.4 Inference of type arguments of the C# Language Specification. Note that there is no mention of the utilisation of type constraints.
The compiler should be smart enough to recognize that BigFoo can't be cast to IEnumerable<IFoo>, but it isn't. It simply sees that it's an IEnumerable<T>, and feels that it's a potential overload candidate (even though the contstraint you defined enforces that T must be IFoo and int can't be cast to IFoo). While it's inconvenient, it's not that big of a deal. Just cast bigFoo to IFoo and the compiler will be happy:
fooContainer.Add((IFoo)bigFoo);
Alternately, you can make your generic overload of Add uglier:
public void Add<T, U>(U group)
where T : IFoo
where U : IEnumerable<T>
{
}
Either way, you have more typing, the second solution eliminates the need to cast calls to Add, but you will have to explicitly declare type on calls to the generic add (which ends up being more code:
fooContainer.Add<IFoo, IEnumerable<IFoo>>(enumerableFoo);
Interesting.... Just tried your sample out. Generics continues to keep me on my toes.
//1 - First preference
public void Add(BigFoo item) { Console.WriteLine("static BigFoo type Add"); }
//2 - Second Preference
public void Add<T>(T item) { Console.WriteLine("Generic Add"); }
//3 - Third preferences
public void Add(IFoo item) { Console.WriteLine("static IFoo interface Add"); }
//4 - Compiles if 1-4 exist. Compile error (ambiguity) if only 3-4 exist. Compile error (cannot convert int to IFoo) if only 4 exists
public void Add<T>(IEnumerable<T> group) where T : IFoo { }