Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 974
Lotok Avatar asked Jan 13 '23 17:01

Lotok


2 Answers

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
like image 103
Tim S. Avatar answered Jan 17 '23 09:01

Tim S.


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.

like image 28
Daniel Hilgarth Avatar answered Jan 17 '23 09:01

Daniel Hilgarth