Search code examples
c#.netcontravariance

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.


Solution

  • 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