首页 > 解决方案 > c# 具有 2 个键的隐式泛型类型

问题描述

我正在为 C# 和 Entity Framework 编写一个通用存储库,并且我试图在不添加一大堆额外接口的情况下让这段代码正常工作。

本质上,我有一个名为的类HavePrimaryKey<TKey>,所有数据库实体都继承自:

public class MemberInfo : HavePrimaryKey<int>

如您所见,它们可以定义类使用的主键类型。

这允许我这样做:

public interface IDbGenericIdRepository<T, TKey> : IDbGenericRepository<T>
    where T : HavePrimaryKey<TKey>

但是,这意味着我需要在创建存储库时定义 EACH 和 EVERY 键:

IDbGenericIdRepository<MemberInfo, int>

实际上,如果能够让编译器从第一种类型必须从第二种类型继承的事实中暗示第二种类型会更好:

IDbGenericIdRepository<MemberInfo>

例如,在上述情况下,由于MemberInfo继承自HavePrimaryKey<int>,那么它希望隐含地知道它需要一个 int ......但它只是呻吟着说“需要 2 个类型的参数”

解决此问题的一种方法是为每种类型使用不同的存储库:

public interface IDbGenericIntIdRepository<T> : IDbGenericRepository<T>
    where T : HavePrimaryKey<int>

public interface IDbGenericStrIdRepository<T> : IDbGenericRepository<T>
    where T : HavePrimaryKey<string>

等等......但我希望可能有一种不那么蛮力的方式来管理这个?

有任何想法吗?

非常感谢,

菲尔。

标签: c#.netentity-framework.net-corerepository-pattern

解决方案


我了解您的思维过程,您的界面与我过去尝试过的非常相似。本指南总结了它:

泛型接口——泛型类可以实现泛型接口或封闭构造的接口,只要类参数列表提供接口所需的所有参数

对他们来说这一切都很好,但在实践中这是有道理的......使用你的定义:

public interface IDbGenericIdRepository<T, TKey> : IDbGenericRepository<T>
    where T : HavePrimaryKey<TKey>

然后我们T 另一个泛型条件上明确定义泛型条件TKey。这意味着我们需要TKey为编译器显式定义类型参数,以便能够检查 T 的类型是否有效。

我们在这里创建了一个依赖,TKey并且T不是同一个类型,T但是必须实现一个版本HavePrimaryKey,这个接口并不关心具体的实际类型TKey是什么,只关心实现接口的类必须定义它。

你还是想知道为什么,虽然你不...

基本上就接口继承而言,我们需要知道实现一个(或多个)接口的类实际上可以为每个接口的每个成员提供不同的实现,并为类本身提供不同的实现!

所以现在我们将介绍一个简单的实现和一些以不同方式实现HavePrimaryKey<TKey>这个接口的示例类,它们在编译器的眼中都是有效的。

public interface HavePrimaryKey<TKey>
{
    T PrimaryKey { get; }
}

public class MemberInfoInt : HavePrimaryKey<int>
{
    public int PrimaryKey { get; }
}

public class MemberInfoString : HavePrimaryKey<string>
{
    public int PrimaryKey { get; }
    string HavePrimaryKey<string> PrimaryKey { get; }
}

另一个有效的实现,但荒谬的是:

public class MemberInfo3 : HavePrimaryKey<int>, HavePrimaryKey<Guid>
{
    public string PrimaryKey { get; }
    int HavePrimaryKey<int>.PrimaryKey { get; }
    Guid HavePrimaryKey<Guid>.PrimaryKey { get; }
}

所以现在如果我们尝试使用您喜欢的符号来实现它,我们可以开始看到两难境地:注意,这是无效的

public class Repo : IDbGenericIdRepository<MemberInfo3> ...

在这种情况下,编译器尝试评估typeof(MemberInfo3) == HavePrimaryKey<?>并且在那里失败,它甚至无法编译应该用于验证第一个表达式的表达式。

没有办法(除了我们已经知道的一种合法方式)告诉编译器我们打算使用哪个特定类型实现来验证MemberInfo3对于T.


C# 是强类型的,这就是我们都喜欢(或讨厌)它的地方,所以在可能存在歧义的地方,我们的天性就是强制并期望我们的类型和类有一个强类型的定义。

这就是为什么您必须从继承的接口中提供所有泛型类型参数的原因。


推荐阅读