Stefan
Stefan

Reputation: 17658

How to set a constraint for a type so it must be of another generic typed type

This is probably asked before but I can't work it out. Maybe if I could get the title right I could goolge it.

I have got this generic repository interface:

public interface IRepository<TEntity>
{
    TEntity Resolve<TEntity>(); // dummy function, just to get the idea
}

I also have a generic unit of work, which is able to resolve a generic repository:

public interface IUnitOfWork
{
    IRepository<TEntity> GetGenericRepository<TEntity>() where TEntity : class;
}

So far so good.

But as real life continues, I would like to create a custom repository, with some specific funtions. So I was thinking: inheritance; like this:

public class SpecialRepository : IRepository<SomeEntityType>
{
    public void SomeSpecialFunction() { };
}

Obviously, this type cannot be resolved with the GetGenericRepository methode so I thought: lets add a extra method to the IUnitOfWork interface:

public interface IUnitOfWork
{
    //same old get generic repository
    IRepository<TEntity> GetGenericRepository<TEntity>() where TEntity : class;

    //the newly added.
    T GetInheretedRepository<T>() where T : class;
}

I want to be able to call the unit of work with the special repository, something like this:

public test()
{
    IUnitOfWork uow = new UnitOfWork();

    //I want to make this call with a constraint on TemplateRepo
    //to enforce it's type: IRepository<T> (which TemplateRepo is)
    var y = uow.GetInheretedRepository<TemplateRepo>(); 
}

The question is: how can I restrict type T in T GetInheretedRepository<T>() where T : class; to be of type: IRepository<TEntity>?

I tried this:

public interface IUnitOfWork
{  
    //the newly added.
    //error: Only class or interface could be specified as constraint
    T GetInheretedRepository<T>() where T : class, IRepository;     }

and

public interface IUnitOfWork
{  
    //the newly added.
    //error: type argument missing
    T GetInheretedRepository<T>() where T : class, IRepository<>; 
}

that doesnt work.

I could drop the constrain as a quick-fix or perhaps create an inherited unit of work, but then; the question still remains.

Upvotes: 1

Views: 85

Answers (2)

Steven
Steven

Reputation: 172646

The way to do this is by adding a second generic type argument, as follows:

TRepository GetInheretedRepository<TRepository, TEntity>() 
    where TRepository : IRepository<TEntity>
    where TEntity : class;

Here you supply both the Repository type and the entity type. This way the C# compiler can check whether or not the type matches. Here's how to call it:

var rep = uow.GetInheretedRepository<SpecialRepository, SomeEntityType>();

rep.SomeSpecialFunction();

This obviously sucks, since you will have to specify both types. But more importantly, this sucks because you have to specify the concrete type, making your code take a dependency on a concrete type; a violation of the Dependency Inversion Principle.

I really would like to advice to to step away from a design where you depend on a concrete type, or even better, step away from a design where you have many methods on a specific repository class, because this violates both SRP, OCP and ISP and this will likely cause maintenance problems later on.

So instead, take a look at the application design that is described in this article.

Upvotes: 2

Sergey Litvinov
Sergey Litvinov

Reputation: 7458

You need to specify second Type like

public interface IUnitOfWork
{  
    //the newly added.
    T GetInheretedRepository<T, TEntity>() where T : class, IRepository<TEntity>; 
}

public interface IRepository<TEntity>
{
    TEntity Resolve(); // dummy function, just to get the idea
}

example that compiles fine - https://dotnetfiddle.net/MmmPil

Upvotes: 1

Related Questions