何时需要指定约束“T:IEquatable <T>”,即使它不是严格要求的?

5
简而言之,我正在寻求关于以下两种方法应该优先选择哪一种(以及为什么)的指导:
static IEnumerable<T> DistinctA<T>(this IEnumerable<T> xs)
{
    return new HashSet<T>(xs);
}

static IEnumerable<T> DistinctB<T>(this IEnumerable<T> xs) where T : IEquatable<T>
{
    return new HashSet<T>(xs);
}
  • 支持使用DistinctA的理由:显然,对T的约束是不必要的,因为HashSet<T>并不需要它,并且因为任何T的实例都可以转换为System.Object,提供与IEquatable<T>相同的功能(即两个方法EqualsGetHashCode)。 (虽然非泛型方法将导致值类型装箱,但这不是我关心的问题。)

  • 支持使用DistinctB的理由:虽然泛型参数约束并不是绝对必要的,但它向调用者明确了该方法将比较T的实例,并因此表明EqualsGetHashCode应该正确地为T工作。 (毕竟,定义一个新类型而没有明确实现EqualsGetHashCode很容易出错,因此该约束可能有助于尽早发现一些错误。)

问题:是否存在已建立和记录的最佳实践,推荐何时指定特定的约束(T : IEquatable<T>),何时不指定?如果没有,上述哪种论点有缺陷?(在这种情况下,我更喜欢深思熟虑的论点,而不仅仅是个人意见。)


由于您追求深思熟虑的论点而非个人观点,因此我会简要评论一下:在这种极不可能发生变化的情况下,我更喜欢对调用者进行可见性。 - Simon Whitehead
2个回答

3
虽然我在Pieter的答案中提到的评论完全是正确的,但我重新考虑了你所指的Distinct确切情况。
这是一个LINQ方法契约,而不仅仅是一个方法。
LINQ旨在成为由各种提供者实现的通用外观。Linq2Objects可能需要IEquatable,Linq2Sql也可能需要IEquatable,但Linq2Sql可能不需要甚至根本不使用IEquatableness,并完全忽略IEquatable性质,因为比较是由DB SQL引擎完成的。
因此,在LINQ方法定义的层次上,指定需要IEquatable没有意义。它会限制和约束未来的LINQ提供者对其特定领域无需关注的事物,而请注意,LINQ实际上往往涉及特定于领域的问题,因为很多时候LINQ表达式和参数实际上从未作为代码运行,而是被分析并重新转换为其他构造(如SQL或XPaths)。在这种情况下省略约束是合理的,因为你真的不知道你未来和未知领域的提供商会真正需要从调用者请求什么。Linq-to-Mushrooms可能需要使用IToxinEquality而不是IEquatable!
然而,当你设计明显将用作可运行代码的接口/契约(而不仅仅是只能作为配置或声明的表达式树)时,我看不出不提供约束的有效理由。

在最后一段加1分。在那之前,我想知道为什么你的答案这么多涉及到LINQ...但是代码作为数据和可运行代码之间的区别很有道理,也是另一个可能的指导方针。谢谢! - stakx - no longer contributing

2

首先考虑何时使用这两种机制会有影响; 我只能想到以下两种情况:

  1. 当代码被翻译成另一种语言(无论是C#的后续版本,还是类似Java的相关语言,或者完全不同的语言,如Haskell)时。在这种情况下,第二个定义显然更好,因为它为翻译器(无论是自动化的还是手动的)提供了更多信息。
  2. 当不熟悉代码的用户阅读它以学习如何调用该方法时。同样,我认为第二种方法显然更好,因为它可以为此类用户提供更多信息。

我无法想象任何情况下第一个定义会被优先选择,并且它实际上比个人偏好更重要。

其他想法?


我认为如果可能的话,所有的需求都应该被陈述为约束条件,因为这样可以让编译器在编译时尽早捕获错误,远在运行时之前。然而,我可以想到一个有些牵强的反驳观点,即用大量约束条件污染你经常使用的接口会对编译的实际持续时间产生负面影响,因为编译器将不得不更频繁地分析和扫描类型层次结构。我不能说我真的相信这一点,即使是这样,我仍然会在可能和适当的地方使用约束条件,并简单地拆分项目 ;) - quetzalcoatl
@quetzalcoatl:如果你看到我的一些代码,你会意识到我发帖的讽刺之处;我坚信只要合理和实用,就应该使用简洁的语言。然而,这篇文章的重点似乎是“何时值得考虑额外的冗长性?”。 - Pieter Geerkens
我必须承认,我也喜欢代码简洁,但作为一个C++本地人,我也是编译器的重度用户,作为第一次测试工具。当我在编写模块或问题域边界时,如果我可以选择在编译时检测错误或缩短代码长度,我会选择前者。当然,这里有一个门槛。+50..150行代码仅仅为了避免一些非常幼稚的错误并不是一个好的选择,可读性和KISS胜出 :) - quetzalcoatl

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