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

3

我希望实现一个完全通用的CRUD API,任何类类型都可以通过上下文传递并进行通用保存、更新等操作。

我有一个存储库可以像这样保存任何模型类型:

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;
        }
    }

这个继承自一个通用接口,该接口实现了所有“add”方法:

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

我不明白为什么不能将泛型类型传递给存储库接口的新实例?我希望能够像下面这样实现:

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

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

这允许我将任何类型传递到方法中,并根据此生成正确的接口。

虽然它允许我传递具体类型,但这有点违背了泛型的初衷。

如果有任何想法或解释为什么不可能,请不吝赐教 - 谢谢。


3
在代码的结尾给定的情况下,我不理解为什么你不能将Data作为通用类型直接传入。你已经知道它是什么,它是一个Data - 它不可能是FooBar。泛型是为了编译时类型安全,而不是运行时类型安全。如果直到运行时才知道类型,则泛型可能不是适合你的解决方案。 - Jamiec
1
我理解数据是一个可能传递的示例类型。其背后的想法是在运行时使用某种类型来实例化接口。如果这不是正确的解决方案,你能告诉我为什么或提供一些进一步的解释吗? - cullimorer
1
很可能这个问题对于这个问题来说过于简单化了。很可能他正在尝试从非泛型世界(在那里你有“object”)转换到泛型世界(在那里你需要对象的编译时类型,而不是“object”)。 - Lasse V. Karlsen
你是否有一个非泛型接口 IRepository 可以使用,这样代码就可以工作了:IRepository repo = new Repository<data.GetType()>();?(如果我们可以修复 <data.GetType()> 部分的话) - Lasse V. Karlsen
我们可以通过反射构建正确类的对象,但是您不会得到一个你知道 <T> 部分的值,这是不可能的。您需要有一个非泛型类型,可以将其存储进去。 - Lasse V. Karlsen
显示剩余4条评论
2个回答

9
尝试使用泛型参数在IRepository本身上实现通用仓储库是没有意义的,因为在这种情况下,术语通用并不是指CLR泛型,而是指仓储库的性质,即使用相同的仓储库实例处理不同类型的实体的能力,与每种类型的仓储库方法(例如ProductsRepository)的方法不同。
将泛型参数放在方法上可以轻松解决问题:
public interface IRepository
{
     T GetById<T>(object id);
     IEnumerable<T> Get<T>(Expression<Func<T, bool>> criteria);
     T Add<T>(T data);
}

使用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;
    }
}

太棒了,伙计!这是最好的解决方案,已经修复了问题。干杯! - cullimorer
为了简洁起见,我省略了约束where T: class。实体框架不允许您在没有它的情况下使用Set<T>。我假设你已经意识到这一点了。 - haim770

1
你遇到了泛型的一种约束(我原本想用“限制”这个词,但那是错误的——它不是限制,恰恰相反)。
泛型在编译时提供了类型安全性,使代码更加简洁,但你必须在编译时知道你要处理的类型。如果你直到运行时才知道类型,那么泛型几乎肯定不是正确的解决方案。
库开发人员知道这一点,他们知道你通常不能或不想指定类型,因此他们经常提供一个非泛型的等效项。非泛型等效项将一个“Type”作为传统参数而不是泛型参数。
例如,我认为你正在使用EntityFramework中的DbSet,但EntityFramework还提供了一个非泛型的DbSet。
因此,你代码中的这行:
var result = _db.Set<T>().Add((T)newItem);

可以写成

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

将其扩展,您可以定义一个非泛型的IRepository

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

并将其实现为:
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;
    }
}

它将会起作用,但你失去了所有类型安全性,使得你的Add方法的响应近乎无用。

总之,一旦你跟随这个兔子洞,我怀疑你会回到通用解决方案 - 它比非通用解决方案先进了数万光年(这就是为什么引入泛型!)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接