Invalid variance in complex generics - c#

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.

Related

Is it not possible to have a covariant interface contain a generic type who's type parameter is the covariant type?

I have a covariant interface and generic class that is unrelated. I'd like the covariant interface to have a property that is an instance of that generic class on the covariant type, like so.
public interface IFoo<out T>
{
Bar<T> barobj { get; set; }
}
public class Bar<T>
{
}
Unfortunately I'm getting an error
Error CS1961 Invalid variance: The type parameter 'T' must be invariantly valid on 'IFoo<T>.barobj'. 'T' is covariant.
Does this mean that it's impossible to have a covariant interface with a generic type that uses the covariant type as the parameter? Am I doing something wrong here?
An interface can only be covariant if it only allows outputs of the generic type. Your interface is not covariant because you can set the value of barobj. If you make the property read-only, then it can be covariant if barobj is covariant. So that means you need a covariant interface for Bar:
public interface IFoo<out T>
{
IBar<T> barobj { get; }
}
public class Bar<T> : IBar<T>
{
}
public interface IBar<out T>
{
}

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();
}
}

Using generic contravariant with IList and IEnumerable

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.

Generic constraint ignores co-variance

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.

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;

Categories

Resources