简体   繁体   English

如何使用通用方法实现接口?

[英]How can you implement an interface with a generic method?

Consider the following classes and interfaces: 考虑以下类和接口:

interface INameable
{
    string Name { get; }
}

interface IRepository<T>
{
    void Add(T obj);
    IEnumerable<T> Values { get; }
}

class Person : INameable
{
    public string Name { get; set; }
    public int Age { get; set; }
}

class Car : INameable
{
    public string Name { get; set; }
    public string Model { get; set; }
}

I now would like to create a Repository class that implements both IRepository<Car> and IRepository<Person> . 我现在想创建一个同时实现IRepository<Car>IRepository<Person>的Repository类。 Here is a sample implementation: 这是一个示例实现:

class Repository : IRepository<Car>, IRepository<Person>
{
    Dictionary<string, object> values = new Dictionary<string, object>();

    void AddValue(INameable o)
    {
        values.Add(o.Name, o);
    }

    IEnumerable<T> ValuesOfType<T>() 
    {
        return values.Values.OfType<T>();
    }

    void IRepository<Car>.Add(Car obj)
    {
        AddValue(obj);
    }

    void IRepository<Person>.Add(Person obj)
    {
        AddValue(obj);
    }

    IEnumerable<Car> IRepository<Car>.Values
    {
        get { return ValuesOfType<Car>(); }
    }


    IEnumerable<Person> IRepository<Person>.Values
    {
        get { return ValuesOfType<Person>(); }
    }
}

This works exactly as expected. 这完全符合预期。 However, it is very repetitive; 但是,这是非常重复的。 the code for the implementation of IRepository<Person> and IRepository<Car> is nearly exactly the same. IRepository<Person>IRepository<Car>的实现代码几乎完全相同。

What I would like to do is implement IRepository for all T where T is a INameable . 我想做的是为所有T实现IRepository ,其中TINameable I tried this: 我尝试了这个:

class Repository2 : IRepository<Car>, IRepository<Person>
{
    // same as before
    Dictionary<string, object> values = new Dictionary<string, object>();

    void AddValue(INameable o)
    {
        values.Add(o.Name, o);
    }

    IEnumerable<T> ValuesOfType<T>() 
    {
        return values.Values.OfType<T>();
    }


    // using generics to implement both the interfaces
    void Add<T>(T obj) where T : INameable
    {
        AddValue(obj);
    }

    void Values<T>() where T : INameable
    {
        return ValuesOfType<T>();
    }

}

However I get errors like: 但是我得到类似的错误:

ConsoleApp.Repository2' does not implement interface member 'ConsoleApp.IRepository<ConsoleApp.Car>.Add(ConsoleApp.Car)'

I'm not sure why the Add<T> and Vales<T> methods aren't being matched - both T's can be set to Person and Car , and then they would exactly match the method type needed. 我不确定为什么Add<T>Vales<T>方法不匹配-可以将T都设置为PersonCar ,然后它们将完全匹配所需的方法类型。

Finally, I tried: 最后,我尝试了:

class Repository3 : IRepository<T> where T is INameable {... }

However, I get an error "Constraints are not allowed on non-generic declarations". 但是,出现错误“非泛型声明中不允许约束”。

What is the best way of solving this problem? 解决此问题的最佳方法是什么?

Note that I am doing this as a method to simply access to a DbContext class (which has references to every table in the application), so instead of passing the full database to each controller, I only pass the data that is needed. 请注意,我这样做是为了简单地访问DbContext类(该类具有对应用程序中每个表的引用)的方法,因此,我没有传递完整的数据库给每个控制器,而是仅传递了需要的数据。 I was doing this to better separate the database from the rest of the app, and to improve testability. 我这样做是为了更好地将数据库与应用程序的其余部分分开,并提高可测试性。 If there is a better way of doing this that could also help. 如果有更好的方法可以帮助您。

In my experience, it is much easier that all the entities, that are to be added to some repository, conform to some interface, say IBaseObject : 以我的经验,要添加到某个存储库中的所有实体都遵循某个接口(例如IBaseObjectIBaseObject

interface IRepository
{
    void Add(IBaseObject obj);
    IEnumerable<IBaseObject> Values { get; }
}

This generally ends up being a good solution because in IBaseObject you could have an identifier so you know when to add or an update an existing record. 通常,这最终是一个很好的解决方案,因为在IBaseObject可以有一个标识符,以便您知道何时添加或更新现有记录。

update: 更新:

Another approach is to use the following pattern, again still relying on IBaseObject : 另一种方法是使用以下模式,同样仍然依赖IBaseObject

interface IRepository
{
   void Add(T obj) where T : IBaseObject;
   IEnumerable<T> GetValues() where T : IBaseObject;
}

You could have an abstract class with the implementation and then just inherit it for specific types. 您可以在实现中创建一个抽象类,然后只为特定类型继承它。

public interface INameable
{
    string Name { get; }
}

public interface IRepository<T>
{
    void Add( T obj );
    IEnumerable<T> Values { get; }
}

public class Person : INameable
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class Car : INameable
{
    public string Name { get; set; }
    public string Model { get; set; }
}

public abstract class AbstractRepository<T> : IRepository<T>
    where T : INameable
{
    // same as before
    Dictionary<string, object> values = new Dictionary<string, object>();

    void AddValue( INameable o )
    {
        values.Add( o.Name, o );
    }

    IEnumerable<T> ValuesOfType<T>()
    {
        return values.Values.OfType<T>();
    }


    // using generics to implement both the interfaces
    public void Add( T obj ) 
    {
        AddValue( obj );
    }

    public IEnumerable<T> Values 
    {
        get
        {
            return ValuesOfType<T>();
        }
    }
}


public class CarRepository : AbstractRepository<Car> { }

public class PersonRepository : AbstractRepository<Person> { }

I think, you should create class that creates repositories. 我认为,您应该创建创建存储库的类。 Something like this: 像这样:

    class Repository<T> : IRepository<T>
    where T : INameable
{
    Dictionary<string, T> values = new Dictionary<string, T>();

    void AddValue(T o)
    {
        values.Add(o.Name, o);
    }

    public void Add(T obj)
    {
        AddValue(obj);
    }

    public IEnumerable<T> Values
    {
        get { return values.Values; }
    }
}

class UnitOfWork
{
    private readonly Dictionary<Type, object> _repositories = new Dictionary<Type, object>();

    public IRepository<T> GetRepository<T>()
        where T : INameable
    {
        object repository;
        if (!_repositories.TryGetValue(typeof (T), out repository))
        {
            repository = new Repository<T>();
            _repositories[typeof (T)] = repository;
        }
        return (IRepository<T>)repository;
    }
}

And use it like that: 并像这样使用它:

        var unitOfWork = new UnitOfWork();

        unitOfWork.GetRepository<Car>().Add(new Car {Name = "Audi"});
        unitOfWork.GetRepository<Car>().Add(new Car { Name = "BMW" });

        foreach (var car in unitOfWork.GetRepository<Car>().Values)
            Console.WriteLine(car.Name);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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