Using generic contravariant with IList and IEnumerable - c#

I'm learning C# generics and making some dummy code for testing purposes. So, I'm testing the in Generic Modifier, which specifies that the type parameter is contravariant.
Given the below interface:
public interface IInterfaceTest<in T>
{
void Method(T value);
void Method(IList<T> values);
void Method(IEnumerable<T> values);
}
When compiling, I'm getting the error message:
[CS1961] Invalid variance: The type parameter 'T' must be invariantly
valid on 'IInterfaceTest.Method(IList)'. 'T' is contravariant.
The error is related only with the line void Method(IEnumerable<T> values). If this line is removed, all works fine.
So my question is: Why can I use the generic contravariant with IEnumerable but does not with IList? Am I forgot something?
Thanks.

The question why it's not allowed for IList<T> has been answered in the comments and linked questions already: IList<T> is invariant in T and so a contra-variant T cannot be used here whatsoever.
What puzzled me at first is the fact that Method(IEnumerable<T>) is allowed here. The strange thing is that variance is "turned around" when you use the T as a type argument for another generic type.
Imagine this.
public interface ITestInterface<in T>
{
void Method(IEnumerable<T> e);
IEnumerable<T> GetMethod(); // illegal
}
public class Animal {}
public class Lion : Animal [}
public class Gnu : Animal {}
ITestInterface<Animal> animals;
ITestInterface<Lion> lions;
ITestInterface<Gnu> gnus;
Now the contra-variance of ITestInterface<in T> in T tells us that you can do
lions = animals;
And when you call lions.Method(e), you can only provide an IEnumerable<Lion>. So the code of Method can only enumerate Lions, which are all Animals as animals.Method() expects. Everything is fine.
On the other hand, the IEnumerable<T> GetMethod() is illegal, because:
gnus = animals;
is legal, and now gnu.GetMethod() would return an IEnumerable<Animal> where you'd expect an IEnumerable<Gnu>. And when you iterated, suprising animals could wait in that sequence.

Related

Is it possible to use the generic "in" and "out" modifier on the same T?

This code is invalid because T can't have the in and out modifier at the same time:
public interface IInOut<in out T>
{
}
But you can do this "workaround":
public interface IInOutWorkaround<in TIn, out TOut>
{
TOut Test(TIn value);
}
public class InOutWorkaround<T> : IInOutWorkaround<T, T>
{
public T Test(T value)
{
throw new NotImplementedException();
}
}
The second example works and the InOutWorkaround class has the same type for TIn and TOut, so why is it not possible to add both modifier to the same T directly in the interface? Or is it possible with a different syntax?
in T says that T can not be used covariantly, and out T says that T can not be used contravariantly. Your in out T would therefore mean that the type can not be used covariantly and can not be used contravariantly, which means it'd be invariant. So in effect that would behave identically to just writing public interface IInOut<T>, because when no in or out modifiers are used the generic type is considered invariant.
In the case of your class InOutWorkaround<T>, T is still invariant, so the fact that you're using it as both an in and out type is fine, because it's invariant, as it meets both restrictions. If you were attempting to have a type that could be used both covariantly and contravariantly, your workaround didn't achieve that, because the T in InOutWorkaround is invariant (because all generic type arguments for all classes are invariant). That generic type argument cannot be used either covariantly or contravariantly.
One could have interfaces IReadable<out T> { T read(int index); }, IWritable<in T> { void write(int index, T dat);, ISplitReadWrite<out Tout, in Tin>:IReadable<Tout>,IWritable<Tin>, and IReadWrite<T>:ISplitReadWrite<T,T>.
If one has a class MyCollection<T> which implements IReadWrite<T>, then a MyCollection<Cat> could be converted to IReadable<Animal>, IWritable<SiameseCat>, or an ISplitReadWrite<Animal,SiameseCat>. Note, however, that the only IReadable<T> that would yield an item that could be stored into a MyCollection<Cat> would be IReadable<Cat>, the only IWritable<T> that could handle everything that might appear in a MyCollection<Cat> would be IWritable<Cat>. The only forms of ISplitReadWrite<Tout,Tin> that would allow one to read out an item and write it back to the same collection without a cast would be those where the two types were the same, and the only such type implemented by MyCollection<Cat> would be ISplitReadWrite<Cat,Cat>.
Note that one could have an interface with methods that could be equally usable with MyCollection<Animal> and MyCollection<SiameseCat>, such as "swap the items in slots i1 and i2 of the same collection", but such an interface wouldn't need any generic parameter at all. Id one has an IPermutable interface, it could include methods like void swapItems(int i1, int i2); which wouldn't have any generic types in their signatures, and thus wouldn't make it necessary for the type to include any generic type arguments.
According to Extending Variant Generic Interfaces specification
The compiler does not infer the variance from the interface that is
being extended. You can create an interface that extends both the interface where the
generic type parameter T is covariant and the interface where it is
contravariant if in the extending interface the generic type parameter
T is invariant.
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }
This example looks like as an exactly your case, T is invariant generic type parameter in InOutWorkaround<T> interface, compiler doesn't infer (or inherit it in extending interface), so your workaround is pointless
public class InOutWorkaround<T> : IInOutWorkaround<T, T>
{
public T Test(T value)
{
throw new NotImplementedException();
}
}

How can I create a covariant extension method on a generic interface in C#?

If the title didn't make sense, here's an example:
interface IEat { void Eat; }
class Cat : IEat { void Eat { nom(); } }
class Dog : IEat { void Eat { nom(); nom();nom(); } }
class Labrador : Dog { }
I'd like to create an extension method like this:
public static void FeedAll(this IEnumerable<out IEat> hungryAnimals) {
foreach(var animal in hungryAnimals) animal.Eat();
}
So I can do this:
listOfCats.FeedAll();
listOfLabs.FeedAll();
listOfMixedHungryAnimals.FeedAll();
Is this possible? Where did I go wrong?
The real-world application here is that "Dog" is a major base class in my application, and there are many subclasses, each of which may have ILists of things from time to time that need to have group operations performed on them. Having to cast them just to call an extension method on a List of an interface they all implement would be suboptimal.
Edit:
I goofed up the example a little. My actual code is using IList, I was hoping to have Count and index-based operations available. Based on the answers below, I guess I'll have to go another direction for the methods that require IList semantics.
IEnumerable is already covariant, so your extension method can accept an IEnumerable<IEat> and an IEnumerable<Dog> instance will be a valid argument, making the extension method apply to variables of those types.
Had the definition of the interface not specified that the generic argument was covariant/contravariant then there would be nothing that you extension method could do to allow that argument to be covariant. If you were using, say, a List which is invariant, there is nothing your extension method can do to allow for the use of covariance.
This will work if you remove the out:
public static void FeedAll(this IEnumerable<IEat> hungryAnimals) {
foreach(var animal in hungryAnimals) animal.Eat();
}
variance applies to parameters of the the interface itself (T in IEnumerable<T> in this case) so a List<Dog> is compatible with IEnumerable<IEat>.
As Servy notes, Enumerable<T> is already covariant, but it doesn't need to be. For older versions where the interface isn't covariant, you could do this:
public static void FeedAll<T>(this IEnumerable<T> hungryAnimals) where T : IEat
{
foreach(var animal in hungryAnimals) animal.Eat();
}
You don't need the out it gets passed by reference so it will work with out it.

Invalid variance in complex generics

I catched this typical compilation error:
Invalid variance: The type parameter 'K' must be covariantly valid on
'ConsoleApplication3.IQuery'. 'K' is contravariant.
I am familiar with the basics of Covariance and Contravariance in C#, but I still can't get why it is wrong:
interface IQuery<in D>
{
}
interface IDct<in K>
{
}
// error here ↓
interface IDctQuery<in K> : IQuery<IDct<K>>
{
}
Please, explain me
UPD
It is interesting that this code is completely valid:
interface IQuery<out D>
{
}
interface IDct<out K>
{
}
interface IDctQuery<out K> : IQuery<IDct<K>>
{
}
What is happening is that by using a contravariant type as a type parameter of another contravariat type, that reverses the direction of the type parameter K. Sounds confusing, but this works fine:
interface IDctQuery<in K> : IQuery<K>
{
}
because K is contravariant in IDctQuery and IQuery. But once you add IDct as a type parameter, the requirement on K is now to be covariant. So you need to change to
interface IDctQuery<out K> : IQuery<IDct<K>>
{
}
Lets say you have two classes, Dog and Animal. A Dog is an Animal and a covariant interface preserves this relationship. So IEnumerable<Dog> can be assigned to an IEnumerable<Animal>.
A contravariant interface reverses this relationship. So an IQuery<Animal> can be assigned to an IQuery<Dog> and an IDct<Animal> can be assigned to an IDct<Dog>.
Your interface declaration:
interface IDctQuery<in K> : IQuery<IDct<K>>
{
}
says that an IDctQuery<Animal> can be assigned to an IDctQuery<Dog> from that it follows that IQuery<IDct<Animal>> can be assigned to IQuery<IDct<Dog>> and because IQuery is contravariant it means that IDct<Dog> can be assigned to IDct<Animal> which is not true because IDict is contravariant and IDct<Animal> can be assigned to an IDct<Dog> but not the other way around.

Why IEnumerable<T> is defined as IEnumerable<out T>, not IEnumerable<T> [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why was IEnumerable<T> made covariant in C# 4?
I was taking a look on MSDN for IEnumerable<T> interface definition, and see:
public interface IEnumerable<out T> : IEnumerable
I was wondering why T is defined as out, why not?
public interface IEnumerable<T> : IEnumerable
What is the reason for this?
More information can be found here.
The out makes the type parameter covariant. That is, you can use either the type or any derived types. Note that out only works this way with generics, it has a different meaning when used in method signatures (though you probably already knew that).
Here is the example taken from the referenced page:
// Covariant interface.
interface ICovariant<out R> { }
// Extending covariant interface.
interface IExtCovariant<out R> : ICovariant<R> { }
// Implementing covariant interface.
class Sample<R> : ICovariant<R> { }
class Program
{
static void Test()
{
ICovariant<Object> iobj = new Sample<Object>();
ICovariant<String> istr = new Sample<String>();
// You can assign istr to iobj because
// the ICovariant interface is covariant.
iobj = istr;
}
}
As you can see, the out in the interface signature allows
you to assign an ICovariant<String> to an ICovariant<Object> variable, as String derives from Object. Without the out keyword, you would be unable to do this, as the types would be different.
You can read more about covariance (and the related contravariance) here.
As other answers have pointed out, IEnumerable was only made covariant in .NET 4. Trying to write code such as:
IEnumerable<Object> strings = new List<string>();
will compile in .NET 4 and later versions, but not in previous versions.
The out type parameter specifier denotes covariance.
In practice,
If I define two interfaces.
interface ISomeInterface<T>
{
}
interface ISomeCovariantInterface<out T>
{
}
Then, I implement them like this.
class SomeClass<T> : ISomeInterface<T>, ISomeCovariantInterface<T>
{
}
Then I try to compile this code,
ISomeCovariantInterface<object> covariant = new SomeClass<string>(); // works
ISomeInterface<object> invariant = new SomeClass<string>(); // fails
// Cannot implicitly convert type 'SomeClass<string>' to 'ISomeInterface<object>'.
// An explicit conversion exists (are you missing a cast?)
The is because the covariant interface allows more derived instances, where as, the standard interface does not.
Fiddle Here
Covariance. This allows a collection to be assigned items of a more specific or derived type than what's specified in its generic param.
IEnumerable<T> wasn't always covariant; this was new to .NET 4, and the reason for the change is explained here.
To achieve this:
class Base {}
class Derived : Base {}
List<Derived> list = new List<Derived>();
IEnumerable<Base> sequence = list;

C# generic overload - Compiler can't determine correct call

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 { }

Categories

Resources