简体   繁体   English

将通用类型传递给通用接口

[英]Passing generic type to a generic interface

I am trying to implement a totally generic CRUD API whereby any class type can be passed in and generically saved, updated etc by the context. 我正在尝试实现一个完全通用的CRUD API,由此可以通过上下文传递任何类类型并对其进行一般性保存,更新等。

I have a repository that saves any model type like this: 我有一个存储库,可以保存任何这样的模型类型:

public class Repository<T> : IRepository<T> where T : class 
    {
        private Context _db = new Context();

        public T Add(T newItem) 
        {
            var result = _db.Set<T>().Add((T)newItem);
            _db.SaveChanges();

            return result;
        }
    }

This inherits from a generic interface that implements all the "add" method: 这从实现所有“ add”方法的通用接口继承:

public interface IRepository<T> where T : class
{
    T Add(T newItem);
}

I am struggling to understand why I can't pass a generic type into a new instance of the repository interface? 我正在努力理解为什么不能将泛型类型传递到存储库接口的新实例中? I would ideally like something like: 理想情况下,我想要这样的东西:

var data = new Data(){
    Id = 1
};

IRepository<data.GetType()> repo = new Repository<data.GetType()>();

Thereby allowing me to pass any type into the method and generating the correct interface off the back of this. 从而允许我将任何类型传递给方法,并在此方法的后面生成正确的接口。

It allows me to pass in a concrete type but that sort of defeats the object of making it generic. 它允许我传递一个具体的类型,但这种方式使将其通用化的目的失败了。

Any ideas around this or explanations of why it's not possible will be greatly appreciated - thanks. 围绕此的任何想法或为什么不可能的解释将不胜感激-谢谢。

Trying to implement a generic repository with a generic parameter on the IRepository itself doesn't make sense because the term generic (in this case) does not refer to CLR generics but to the nature of the repository, which is the ability to work with different types of entities using the same repository instance, in contrast to repository-per-type approach (like ProductsRepository for example). 尝试在IRepository本身上使用泛型参数实现泛型存储库没有任何意义,因为术语泛型 (在这种情况下)不是指CLR泛型,而是指存储库的性质,即可以与其他库一起使用与每种类型的存储库方法(例如,如ProductsRepository )相比,使用相同存储库实例的实体类型也有所不同。

Placing the generic parameter on the methods can easily solve the problem: 将通用参数放在方法上可以轻松解决问题:

public interface IRepository
{
     T GetById<T>(object id);
     IEnumerable<T> Get<T>(Expression<Func<T, bool>> criteria);
     T Add<T>(T data);
}

Possible implementation using EntityFramework would be: 使用EntityFramework的可能实现是:

public class EntityFrameworkRepository : IRepository
{
    public T GetById<T>(object id)
    {
        return this.Set<T>().Find(id);
    }

    public IEnumerable<T> Get<T>(Expression<Func<T, bool>> criteria)
    {
        return this.Set<T>().Where(criteria);
    }

    public T Add<T>(T data)
    {
        this.Set<T>().Add(data);
        return data;
    }
}

You've hit upon a constraint of generics (I was going to use the word "limitation", but that's wrong - it's not a limitation, quite the opposite). 您遇到了泛型约束 (我本来要使用“局限性”一词,但这是错误的,它不是局限性,相反)。

Generics provide you type safety at compile-time, it allows cleaner code, but you must know the type you're working with at compile-time. 泛型在编译时为您提供类型安全性,它允许使用更简洁的代码,但您必须在编译时知道所使用的类型。 If you do not know the type until run time, as it seems you don't then generics is almost certainly the wrong solution. 如果您似乎直到运行时才知道类型,那么泛型几乎肯定是错误的解决方案。

Library developers know this, they know you often can not or do not want to specify the type, so often they provide a non-generic equivalent. 库开发人员知道这一点,他们知道您经常不能或不想指定类型,因此他们经常提供非泛型的等效项。 The non-generic equivalent takes a Type as a traditional parameter rather than a generic parameter. 非泛型等效项将Type作为传统参数而不是泛型参数。

For example, I think you're using the DbSet<T> in EntityFramework, but EntityFramework also provides a non generic DbSet . 例如,我认为您在EntityFramework中使用DbSet<T> ,但是EntityFramework还提供了一个非通用的DbSet

So this line in your code: 因此,您的代码中的这一行:

var result = _db.Set<T>().Add((T)newItem);

Could be written as 可以写成

var result = _db.Set(newItem.GetType()).Add(newItem);

Expanding this out, you could define a non-generic IRepository : 扩展这一点,您可以定义一个非通用的IRepository

public interface IRepository
{
    object Add(object newItem);
}

And implement it as 并实现为

public class Repository : IRepository
{
    private Context _db = new Context();
    private Type entityType;
    public Repository (Type entityType)
    {
        this.entityType= entityType;
    }

    public object Add(object newItem) 
    {
        var result = _db.Set(entityType).Add(newItem);
        _db.SaveChanges();

        return result;
    }
}

It will work, but you've lost all type safety, making the response from your Add method near-on useless. 它将起作用,但是您已经失去了所有类型的安全性,从而使Add方法的响应几乎失效。

All in all, once you've followed this rabbit hole I suspect you'll return to the generic solution - it's light-years ahead of the non-generic solution (Thats why generics were added!) 总而言之,一旦您遵循了这个兔子漏洞,我怀疑您会回到通用解决方案-比非通用解决方案快几年(这就是为什么要添加通用!)

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

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