Jonas
Jonas

Reputation: 4584

Implementing multiple generic interfaces - type error

I'm trying to do something like this:

public interface IRepository<T>
{
  T Get<T>(int id);
}

public interface IFooBarRepository : IRepository<Foo>, IRepository<Bar>
{
}

IFooBarRepository repo = SomeMethodThatGetsTheActualClass();
Foo foo = repo.Get<Foo>(1);

I'm getting a warning:

Type parameter 'T' has the same name as the type parameter from outer type 'IRepository'

And an error:

The call is ambiguous between the following methods or properties: 'IRepository.Get(int)' and 'IRepository.Get(int)'

Any thoughts on how I can make this pattern work?

Upvotes: 5

Views: 4904

Answers (5)

LBushkin
LBushkin

Reputation: 131676

Unfortunately, you can't. This isn't how generics are designed to work in C#. If you go with this pattern, you will be forced to always disambiguiate which interface version you wish to call Get() on by casting repo:

IFooBarRepository repo = SomeMethodThatGetsTheActualClass(); 
Foo foo = ((IRepository<Foo>)repo).Get(1); 

which probably isn't what you want.

You could, of course, implement proxying methods in the implementation of IFooBarRepository that return the right types ... but again, this may not be what you're looking for.

You can, however, create properties on IFooBarRepository that improve the syntax thusly:

interface IFooBarRepository : IRepository<Foo>, IRepository<Bar>
{
    IRepository<Foo> FooGetter { get; }
    IRepository<Bar> BarGetter { get; }
}

Now you can write:

IFooBarRepository repo = SomeMethodThatGetsTheActualClass(); 
Foo foo = repo.FooGetter.Get(1); 
Bar bar = repo.BarGetter.Get(2);

In general, it's desirable to avoid generic methods that don't accept as a formal parameters arguments of the types of the type parameters. In your case, you're trying to encode the semantics of repositories directly into the type system. You may be better off dividing this reponsibility into a type that expresses the behavior of the repository and a separate type that expresses the behavior of fetching objects.

Upvotes: 4

x0n
x0n

Reputation: 52420

Instead of:

Foo foo = repo.Get<Foo>(1);

use

Foo foo = ((IRepository<Foo>)repo).Get(1);

Which kind of defeats the overall feeling of using generics to avoid casting, but unfortunately what you're doing is not possible without providing more hints to the compiler.

Upvotes: 1

Steven Sudit
Steven Sudit

Reputation: 19620

Use explicit implementation. To specify which Get, first cast up to the appropriate interface.

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500415

To call the appropriate one, you'll need to make the compiler think of the expression in the appropriate way:

IFooBarRepository repo = SomeMethodThatGetsTheActualClass();
IRepository<Foo> fooRepo = repo;
Foo foo = fooRepo.Get(1);

Note that you could just cast it in one statement:

IFooBarRepository repo = SomeMethodThatGetsTheActualClass();
Foo foo = ((IRepository<Foo>)repo).Get(1);

... but that looks pretty ugly to me.

That deals with calling the method. Implementing both interfaces in one class is the next hurdle... because they'll have the same signature in terms of parameters. You'll have to implement at least one of them explicitly - and it might cause less confusion if you did both:

public class FooBarRepository : IFooBarRepository
{
    Foo IRepository<Foo>.Get(int id)
    {
        return new Foo();
    } 

    Bar IRepository<Bar>.Get(int id)
    {
        return new Bar();
    } 
}

EDIT: You'll also need to make Get a non-generic method: currently you're trying to redeclare the type parameter T in IRepository<T>.Get<T>; you just want to use the existing type parameter of IRepository<T>.

Upvotes: 7

Darin Dimitrov
Darin Dimitrov

Reputation: 1038770

You don't need to repeat T once again at the method declaration. It is already declared at the interface:

public interface IRepository<T>
{
    T Get(int id);
}

Also notice that you need to explicitly implement the IFooBarRepository interface because only the return type of the Get method differs which is not possible.

Upvotes: 2

Related Questions