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 class
es (reference types), you should include a second restriction:
public interface IRepository<T, in TKey>
where T : class
where TKey : class