【问题标题】:Generic Type Inference in C#C# 中的泛型类型推断
【发布时间】:2014-09-22 09:06:06
【问题描述】:

假设 C# 中有这些泛型类型:

class Entity<KeyType>
{
    public KeyType Id { get; private set; }
    ...
}

interface IRepository<KeyType, EntityType> where EntityType : Entity<KeyType>
{
    EntityType Get(KeyType id);
    ...
}

以及这些具体类型:

class Person : Entity<int> { ... }

interface IPersonRepository : IRepository<int, Person> { ... }

现在PersonRepository 的定义是多余的:PersonKeyTypeint 的事实已明确说明,尽管可以从Person 是@987654328 的子类型这一事实推断出@。

如果能够像这样定义IPersonRepository 那就太好了:

interface IPersonRepository : IRepository<Person> { ... }

让编译器找出KeyTypeint。有可能吗?

【问题讨论】:

  • 它应该如何知道KeyType?你能展示你所期望的完整语法吗?您的最后一个接口定义看起来不完整。
  • 我假设最后一行代码应该是interface IPersonRepository : IRepository&lt;Person&gt; { ... }
  • 现在尝试在您的IPersonRepository 中编写Get 方法,而不必在其中硬编码int,您会意识到这个提议没有太多好处。
  • 当然没有找到 KeyType:你没有在你的接口定义中声明它。
  • 我想知道为什么人们试图让一切都通用。具有通用键类型的通用实体的通用存储库... 抽象的抽象使生活变得非常复杂。我认为重用在我们的行业中被高度高估了。

标签: c# generics type-inference


【解决方案1】:

不,C# 的类型系统不够先进,无法表达您想要的。所需的功能称为高级类型,通常在强类型函数语言(Haskell、OCaml、Scala)中找到。

回到过去,你希望能够写作

interface IRepository<EntityType<EntityKey>> {
  EntityType<EntityKey> Get(KeyType id);
}

interface PersonRepository : IRepository<Person> {
  Person Get(Int id);
}

但在 C# 中,没有办法表示类型 EntityType,或者换句话说,类型参数有一些泛型参数并在您的代码中使用该泛型参数。

旁注:存储库模式是邪恶的,必须死于火灾。

【讨论】:

  • 精彩的答案。但是为什么存储库模式是邪恶的呢?您会建议哪些替代方案?
  • 存储库模式没有用例。它对你不利,因为它们通常是你在下面使用的 ORM 的子接口,它只是转发调用。它对模拟和/或测试没有帮助(当您必须考虑数据库提供者提供和​​不提供哪些表达式时,您不能认真模拟 where/get),它不会使您的代码更多模块化,它增加了一层额外的复杂性,并且在极少数情况下你要换掉你的 ORM(是的,正确的),由于不同的性能特征,你可能不得不深入研究业务逻辑。
  • 至于替代方案,只需直接使用您的 ORM,而不是将其包装在存储库中。
  • 你说得有道理,但我还是觉得直接访问 ORM 实现不方便。诚然,存储库上的大多数操作只是将调用转发到 ORM,但恕我直言,保护业务操作免受访问基础设施的细节影响仍然是有意义的。说UserRepository.ActiveUsersSession.CreateCriteria&lt;User&gt;().Add(Restrictions.Eq("_active", true)).List&lt;User&gt;() 之类的说法不同(而且更清楚)。除此之外,同样的性能论点是否适用于直接访问数据库,根本不使用 ORM?
  • 让我重新表述一下 :) 通用存储库 (UserRepository.Save(User)) 没有多大作用。将诸如 ActiveUsers 之类的数据访问内容与您的业务逻辑隔离开来是有意义的,尤其是当其中有很多不是业务逻辑的数据逻辑时。我个人会为 NHibernate 选择 ISession 上的扩展方法,或者将它们直接放在 EF 的 DbContext 中,而不是包装它们。
【解决方案2】:

假设我们要声明

interface IPersonRepository : IRepository<Person> { }

这需要有一个带有一个类型参数IRepository&lt;EntityType&gt; 的通用接口。

interface IRepository<EntityType> where EntityType : Entity<KeyType>
{
    EntityType Get(KeyType id);
}

在第一行的末尾,您引用了一个名为KeyType 的东西,它没有被声明或定义。没有称为“KeyType”的类型。

这会起作用:

interface IRepository<EntityType> where EntityType : Entity<int>
{
    EntityType Get(int id);
}

或者这个:

interface IRepository<EntityType> where EntityType : Entity<string>
{
    EntityType Get(string id);
}

当然,您不能同时拥有两个相互冲突的定义。显然,您对此并不满意,因为您希望能够以这样的方式定义您的 IRpository 接口,以便它也可以与其他密钥类型一起使用。

好吧,你可以,如果你在键类型中使其通用:

interface IRepository<KeyType, EntityType> where EntityType : Entity<KeyType>
{
    EntityType Get(KeyType id);
}

还有一种替代方法:

interface IRepository<KeyType>
{
    EntityType<KeyType> Get(KeyType id);
}

现在你可以定义

class PersonRepository : IRepository<int>
{
    public EntityType<int> Get(int id) { ... }
}

显然,您不会对此感到满意,因为您想声明 Get 方法必须返回 Person,而不仅仅是任何 Entity&lt;int&gt;

具有两个类型参数的泛型接口在唯一的解决方案中。实际上,它们之间存在必需的关系,如约束中所表达的那样。但是这里没有冗余:为 type 参数指定 int 并没有携带足够的信息。

如果我们说

class PersonRepository : IRepository<int, Person>
{
    public Person Get(int id) { ... }
}

确实存在冗余:当类型参数Person已经指定时,指定类型参数int是多余的。

有可能使用一种语法来推断 KeyType。例如,Patrick Hoffman 建议:

class PersonRepository : IRepository<EntityType: Person>. 
{
    public Person Get(int id) { ... }
}

虽然理论上可行,但我担心这会给语言规范和编译器增加很多复杂性,而收益却很小。事实上,有任何收获吗?您当然不会节省击键!比较这两个:

// current syntax
class PersonRepository : IRepository<int, Person>
{
    public Person Get(int id) { ... }
}

// proposed syntax
class PersonRepository : IRepository<EntityType: Person>
{
    public Person Get(int id) { ... }
}

语言就是这样,对我来说还不错。

【讨论】:

  • interface IPersonRepository : IRepository&lt;int, Person&gt; 知道IRepository 的第一个类型参数应该与Person 相同,因此在IPersonRepository 的声明中明确声明它是多余的,因为它已经被声明了:class Person : Entity&lt;int&gt;。我遇到的问题是告诉编译器它们确实应该是相同的,即从 Person 的声明中推断类型。
【解决方案3】:

不,不可能这样写,因为它不会推断类型(编译器不会)。

应该可以这样做(尽管您需要成为 C# 编译器团队的一员才能获得它),因为没有其他值可以将 KeyType 的值放入 @987654322 的类型参数中@。不能放入派生类型或基类类型。

正如其他人评论的那样,它可能会使您的代码过于复杂。此外,这只适用于Entity&lt;T&gt; 是一个类的情况,当它是一个接口时,它不能推断类型,因为它可以有多个实现。 (也许这就是他们没有建立这个的最终原因)

【讨论】:

  • you need to be part of the C# compiler team though to get it in - 好吧,如果你不介意没有人能够编译你的代码,你总是可以分叉 Roslyn :)
  • @Rawling:我期待你的实施;)
  • 语法到底是什么样的? interface IRepository&lt;EntityType&gt; where EntityType : Entity&lt;KeyType&gt; 是具有明确含义的现有语法。当然,如果KeyType 类型不存在,编译器将无法编译。另见我的回答。
  • 也许是IRepository&lt;EntityType: Person&gt;。这使得 KeyType '可选'。
  • 这可能真的有效。我担心它会给语言规范和编译器增加很多复杂性,但收益很小。我希望编译器团队把时间花在更有用的事情上。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-29
  • 1970-01-01
相关资源
最近更新 更多