简体   繁体   中英

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?

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> .

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.

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM