Contravariant Value Types - c#

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

Related

Extension methods on enumerable-derived classes cannot infer type arguments

We are currently working with generics on extensions for enumerable-derived classes.
Apparently, the compiler produces an error CS0411 with extension methods if they are called on a class derived from IEnumerable<T> and return values that are of the enclosed T type.
The following example reproduces the error:
public class Item { }
public class CollectionType : IEnumerable<Item>
{
public IEnumerator<Item> GetEnumerator()
{
throw new NotImplementedException();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
public static class CollectionExtension
{
public static T[] DoSomething<T, TCollection>(this TCollection source)
where TCollection : IEnumerable<T>
{
throw new NotImplementedException();
}
}
public class Test
{
public void TestMethod()
{
var collection = new CollectionType();
collection.DoSomething();
collection.DoSomething<Item, CollectionType>(); // This works fine
}
}
Calling DoSomething() will produce the following error:
The type arguments for method 'T[] [...].CollectionExtension.DoSomething<T,TCollection>(this TCollection)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
How can this happen if TCollection is bound to be of type IEnumerable<T> and T is also defined as a generic type parameter of DoSomething()?
Calling the extension method on the CollectionType should provide DoSomething() with both generic types.
It would not be possible to use the DoSomething() method as-is without specifying the generic types explicitly. The class IEnumerable<T> from which it seemingly would be possible to infer the type of T is located in the type constraint declaration, unfortunately C# "...cannot infer the type parameters only from a constraint or return value". See the details here:
[https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/generic-methods]
This is issue can be solved in a different ways, you could explicitly specify the type as you have done in the example code - collection.DoSomething<Item, CollectionType>(), or add to DoSomething() an argument of type T, in that case C# will be able to infer the type, or get rid of the generic type TCollection, replacing it with IEnumerable<T> in the following way:
public static T[] DoSomething<T>(this IEnumerable<T> source)
{
throw new NotImplementedException();
}
The last one I think would be a cleaner way.

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

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

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.

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>.

Categories

Resources