【问题标题】:Should generic constraints be preferred to using interfaces as parameter types?是否应该优先使用泛型约束而不是使用接口作为参数类型?
【发布时间】:2011-04-01 08:16:29
【问题描述】:

考虑这个微不足道的函数:

public static bool IsPositive(IComparable<int> value)
{
    return value.CompareTo(0) > 0;
}

现在,如果我将 int 传递给此方法,它会被装箱。因此,将上述方法定义如下不是更好吗?

public static bool IsPositive<T>(T value) where T : IComparable<int>
{
    return value.CompareTo(0) > 0;
}

以这种方式使用通用约束,我可以实现与上面的代码完全相同的功能,另外还有一个好处是不需要装箱(因为对IsPositive&lt;int&gt; 的调用接受int 类型的参数)。

上面的示例代码显然毫无意义。但我更广泛的问题是:总是以后一种方式定义方法(使用通用约束而不是具有某种接口类型的参数)是否有意义,以避免潜在的值类型的装箱?

我怀疑答案很可能是“是的,但它需要更多的输入,并且在许多情况下遇到值类型的可能性很小,例如当一个方法接受一些 IEnumerable&lt;T&gt; 时。”但我想知道这些方法之间是否存在更大的差异,这让我现在无法理解。

【问题讨论】:

  • 装箱/专业化不只是关于通用类型而不是函数吗?
  • @Dario:我不确定你在问什么。如果将值类型传递给接受接口类型参数的方法,则该值将被装箱。这就是为什么可以在方法中将本地参数设置为null 的原因。另一方面,使用通用方法,IsPositive&lt;T&gt; 中的本地参数是 T 类型,它可以是值类型 引用类型。这有意义吗?
  • @Dan - 在一般情况下,值类型仍然需要装箱。 IL 允许通过 constrained 指令以统一的方式使用类型,但是当编译为机器代码时,它与以前的装箱方法没有什么不同 - 没有任何好处。
  • @Mark H:如果你说的是真的,那么这显然是最令人信服的理由。不过,你确定吗?你能证明这一点吗?
  • 也许不会,我会对两者进行基准测试:/

标签: .net generics interface constraints boxing


【解决方案1】:

一个问题是通用约束实际上并不是签名的一部分。如果你有...

static T Method<T>(T value) where T : ICompareable<int>

...和...

static T Method<T>(T value) where T : IEnumerable<int>

...编译器无法知道哪个是哪个。

然后调用 Eric Lippert...

【讨论】:

  • 我还在等待那一天我可以说where T.Parse(string)
  • 啊,很好的观点。好的,我知道至少有某事是我没有想到的。但我等待更多答案(因为我确信还有更多我没有考虑在内)。
  • @Matthew Whited:我很困惑。 where T.Parse(string) 对于约束来说似乎是一个相当糟糕的主意。还是您正在等待它出现在问题中?
  • @Brian,这个想法是允许基于静态方法约束通用参数。然后,您可以在基于您的方法的类型上使用方法(例如.Parse(...))。这将允许您编写不需要反射的通用适配器。
  • 顺便说一句,现在这个想法需要对 CLR 进行更改,例如类似于静态方法的虚拟查找表。现在静态方法调用是在编译时确定的,因此不能与静态方法一起使用(您可以尝试手动编写 IL 代码,但它会在运行时抛出异常,因为它无法解析方法调用。)
【解决方案2】:

cmets 对关于方法调用是否在传递参数之后引发装箱的问题存在一些混淆。

当您对类型为带有约束的类型参数的表达式调用虚拟方法时,C# 编译器会发出 constrained.callvirt 指令。正如人们所希望的那样,这是正确的。拳击只有在绝对必要时才会发生。

有关受限虚拟调用的精确装箱语义的详细信息,请阅读文档:

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.constrained.aspx

【讨论】:

    【解决方案3】:

    另一个问题是泛型约束在参数化类型的泛型类型参数上时,例如

    static bool AreAllTheSame<T>(IEnumerable<T> something)
      where T : IEquatable<T>
    

    并非总是可以通过这种方式转换泛型类型参数,除非您像这样引入第二个类型参数:

    static bool AreAllTheSame<S, T>(S something)
      where S : IEnumerable<T>
      where T : IEquatable<T>
    

    这看起来不太对。

    【讨论】:

    • 但这完全没问题(除了我认为您需要更改代码以读取where S : IEnumerable&lt;T&gt; where T : IEquatable&lt;T&gt; -- 两个where 约束,而不是一个带有逗号的约束)。你是说它不是一个选项,因为它“看起来不正确”?
    • @Dan:我已经解决了这个问题。我说它看起来不正确的原因是你需要一个额外的类型参数来安抚约束引擎。如果您查看方法的签名和意图,两个参数之一是完全多余的。此外,这种方式 T 几乎对​​ API 使用者隐藏,即使它是(恕我直言)最相关的类型参数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-16
    • 2014-09-23
    • 2020-06-28
    • 1970-01-01
    • 2014-10-19
    • 1970-01-01
    • 2013-08-16
    相关资源
    最近更新 更多