116

在未能获得以下内容进行编译后:

public class Gen<T> where T : System.Array
{
}

有错误

约束不能是特殊类“System.Array”

我开始想,究竟什么“特殊班级”?

System.Enum当人们在通用约束中指定时,他们似乎经常遇到相同类型的错误。我用 , 和 也得到了System.Object相同System.DelegateSystem.MulticastDelegate结果System.ValueType

还有更多吗?我在 C# 中找不到有关“特殊课程”的任何信息。

此外,这些类有什么特别之处以至于我们不能将它们用作泛型类型约束?

4

7 回答 7

106

从 Roslyn 源代码中,它看起来像一个硬编码类型列表isValidConstraintType

switch (type.SpecialType)
{
    case SpecialType.System_Object:
    case SpecialType.System_ValueType:
    case SpecialType.System_Enum:
    case SpecialType.System_Delegate:
    case SpecialType.System_MulticastDelegate:
    case SpecialType.System_Array:
        // "Constraint cannot be special class '{0}'"
        Error(diagnostics, ErrorCode.ERR_SpecialTypeAsBound, syntax, type);
        return false;
}
于 2015-04-30T07:57:29.970 回答
42

我在 2008 年发现了 Jon Skeet 对类似问题的评论:为什么支持System.Enum约束。

我知道这有点离题,但他向 Eric Lippert(C# 团队)询问了这个问题,他们提供了这个答案:

首先,你的猜想是正确的;对约束的限制基本上是语言的人工制品,而不是 CLR。(如果我们要实现这些功能,我们希望在 CLR 中更改一些关于如何指定可枚举类型的小事情,但主要是语言工作。)

其次,我个人希望拥有委托约束、枚举约束以及指定当今非法约束的能力,因为编译器正试图将您从自己手中拯救出来。(也就是说,使密封类型作为约束合法,等等。)

但是,由于日程安排的限制,我们可能无法将这些功能融入到该语言的下一个版本中。

于 2015-04-30T08:09:50.677 回答
25

根据MSDN,它是一个静态的类列表:

编译器错误 CS0702

约束不能是特殊类“标识符”以下类型不能用作约束:

  • 系统对象
  • 系统数组
  • 系统委托
  • 系统枚举
  • 系统值类型。
于 2015-04-30T07:56:59.980 回答
14

根据 C# 4.0 语言规范(编码:[10.1.5] 类型参数约束)说明了两件事:

1] 类型不能是对象。因为所有类型都派生自对象,所以如果允许,这样的约束将不起作用。

2] 如果 T 没有主约束或类型参数约束,则它的有效基类是 object。

当您定义一个泛型类时,您可以对客户端代码在实例化您的类时可用于类型参数的类型类型应用限制。如果客户端代码尝试使用约束不允许的类型来实例化您的类,则结果是编译时错误。这些限制称为约束。使用 where 上下文关键字指定约束。 如果要将泛型类型限制为引用类型,请使用 :class。

public class Gen<T> where T : class
{
}

这将禁止泛型类型成为值类型,例如 int 或 struct 等。

此外,约束不能是特殊类“标识符”以下类型不得用作约束:

  • 系统对象
  • 系统数组
  • 系统委托
  • 系统枚举
  • 系统值类型。
于 2015-04-30T08:15:26.147 回答
12

框架中的某些类有效地将特殊特征传递给从它们派生的所有类型,但它们本身并不具备这些特征。CLR 本身并没有禁止使用这些类作为约束,但是受约束的泛型类型不会像具体类型那样获得非继承特性。C# 的创建者决定,因为这样的行为可能会让一些人感到困惑,并且他们看不到它有任何用处,所以他们应该禁止这样的约束,而不是允许它们像在 CLR 中那样行事。

例如,如果允许一个人写void CopyArray<T>(T dest, T source, int start, int count):可以将dest和传递source给期望类型为参数的方法System.Array;此外,人们将获得兼容数组类型的编译时验证destsource但无法使用[]运算符访问数组的元素。

无法Array用作约束通常很容易解决,因为void CopyArray<T>(T[] dest, T[] source, int start, int count)在前一种方法可以工作的几乎所有情况下都可以工作。但是,它确实有一个弱点:前一种方法可以在一个或两个参数是类型的System.Array情况下工作,而拒绝参数是不兼容的数组类型的情况;添加两个参数都是类型的重载System.Array将使代码接受它应该接受的其他情况,但也会使其错误地接受它不应该接受的情况。

我发现取缔大多数特殊限制的决定令人讨厌。唯一具有零语义意义的将是System.Object[因为如果这是合法的约束,任何东西都会满足它]。 System.ValueType可能不会很有用,因为类型的引用ValueType与值类型并没有太多共同之处,但在涉及反射的情况下它可能有一些价值。两者System.Enum都有System.Delegate一些实际用途,但是由于 C# 的创建者没有想到它们,因此它们被无缘无故地取缔了。

于 2015-04-30T13:54:15.480 回答
10

通过 C# 第 4 版可以在 CLR 中找到以下内容:

主要约束

类型参数可以指定零个主要约束或一个主要约束。主要约束可以是标识非密封类的引用类型。您不能指定以下特殊引用类型之一:System.ObjectSystem.ArraySystem.DelegateSystem.MulticastDelegateSystem.ValueTypeSystem.EnumSystem.Void。指定引用类型约束时,您向编译器保证指定的类型参数将是相同类型或从约束类型派生的类型。

于 2015-04-30T07:59:05.057 回答
5

我不认为,“特殊类”/“特殊类型”存在任何官方定义。

您可能会认为它们是一种类型,它不能与“常规”类型的语义一起使用:

  • 你不能直接实例化它们;
  • 您不能直接从它们继承自定义类型;
  • 有一些编译器魔法可以使用它们(可选);
  • 直接使用他们的实例至少没用(可选;想象一下,你已经在上面创建了泛型,你要写什么泛型代码?)

PS我会添加System.Void到列表中。

于 2015-04-30T07:56:16.667 回答