C# 泛型类型约束

3

这不应该是有效的C#代码吗?

class A<T> where T : class {

    public void DoWork<K>() where K : T {

        var b = new B<K>(); // <- compile time error
    }
}

class B<U> where U : class {

}

编译器报错如下:
error CS0452: 必须将类型 'K' 作为引用类型,才能在泛型类型或方法 'ConsoleApplication1.B' 的参数 'U' 中使用它。
编译器难道不能推断出K被限制为类型T或派生自T,因此它显然应该是一个引用类型(T被限制为引用类型)吗?

3
这只是另一个“为什么编译器不能自己解决这个问题”的问题。归根结底,这是因为编译器开发人员的时间成本很高,而Microsoft有限的时间需要合理分配。无论编译器能做多少,总会出现一些边缘情况,此时需要你介入并提供帮助。 - James Gaunt
嗯,编译器开发可能不便宜。我在想这种行为是有意为之还是无意中被忽略了。 - xenry
@James:在这种情况下,编译器正在执行规范所规定的操作。 - Jeff Yates
1
@Jeff - 当然是这样的。我并不是在说这是一个错误或疏忽。规范和编译器是一起编写的。没有一个团队编写规范,另一个团队说“我们没有时间做那个”。有一个团队说“这是我们有时间/金钱去做的”,然后编写编译器和规范来反映这一点。 - James Gaunt
@James:我认为规范应该首先编写,并且要考虑到语言提供的功能以及时间和预算限制。话虽如此,无论编译器实现如何,对我来说都很明显,让编译器推断这些信息是一项高成本、低回报的事情,因此可能很容易被忽略。我相信C#大师们中的一个可以更详细地解释这个问题。 - Jeff Yates
6个回答

8

我之前的回答是错误的,已经删除了。感谢用户configurator指出我的错误。

编译器难道不能推断出K被限制为类型T或派生自T,因此它显然应该是引用类型(T被限制为引用类型)吗?

不可以。

K被限制为类型T或从T派生的类型。T被限制为引用类型。这并不意味着K是引用类型。例如:对象是引用类型,而整数是从对象派生出来的类型。如果T是对象,则K可以是整数,但整数不是引用类型。


7
约束条件在指定类型参数时应用。尽管K被指定为U,但K的类型未被指定。由于U要求其类型为引用类型,编译器正在确认K确实是引用类型,但它无法确定。因此,您需要明确声明它将是引用类型。 规范在第4.4.4节中指出:
对于每个where子句,与命名类型参数对应的类型参数A将针对每个约束进行检查...
稍后又说:
如果一个或多个类型参数的约束未被给定的类型参数满足,则会发生编译时错误。
由于类型参数不会继承,因此最后一点表明K不会从T继承约束。 更新
尽管我的结论似乎是正确的,但是我的证据有些不太可靠,正如Eric Lippert's response中澄清的那样。在那里,Eric表示规范的正确部分是:

如果一个类型参数具有引用类型约束或其有效基类不是对象或System.ValueType,则该类型参数被认为是引用类型。


谢谢,但我认为你的第一段不够清晰,或者你把T、K、U弄混了。如果你能为未来的读者纠正它 =) - xenry
@xenry:你说得对。我把T和U搞混了。已经更正了。谢谢! - Jeff Yates
看起来Eric Lippert已经删除了你所链接的答案。 - Randy Levy
@Tuzo:是的,看起来是这样。我会尽力将他的回复要点融入进来。 - Jeff Yates

3

这种情况下,约束条件不会级联。每个通用签名都有自己独特的约束条件,独立于任何可能隐含的子约束条件进行评估。即使T已经暗示了K的类声明,您仍需要对K、T和U进行类声明。


2

试试这个:

class A<T> where T : class
{
    public void DoWork<K>() where K : class, T 
    {
        var b = new B<K>(); // <- compile time error
    }
}

class B<U> where U : class
{
}

不幸的是,编译器似乎无法推断。但是,如果您为K添加更多限制条件,那么您应该可以继续。


我认为这个观点是错的。它之所以编译通过,是因为您在U上删除了类约束。指定行上的编译器错误并不是由于缺少new()引起的,因为正在实例化B<K>,而我们知道B<K>是一个具有无参构造函数的类。 - James Gaunt
好的,我重新修改了我的回答以融入你的反馈意见。 - RQDQ
这并不是关于编译器是否“足够聪明”的问题 - 编译器正在满足规范,该规范指出“约束永远不会被继承”。 - Jeff Yates
每个定义的值类型都与两种实体相关联--其中一种表现为继承System.ValueType的类,另一种表现为值类型且不继承任何内容;从后者到前者进行隐式扩展转换,而缩小转换可以反过来进行。不幸的是,没有办法指定存储位置应该保存对第一种实体的引用。如果一个类型派生自System.ValueType,则声明为该类型的任何存储位置都将像不继承任何内容的值类型一样运行。 - supercat

1

我认为你需要明确指定K:class和T。

class A<T> where T : class {

    public void DoWork<K>() where K : class, T {

        var b = new B<K>(); // <- compile time error
    }
}

class B<U> where U : class {

}

0
你应该这样做:
class A<T> where T : class
{

    public void DoWork<K>() where K: class, T
    {

        var b = new B<K>(); // <- compile time error
    }
}

class B<U> where U : class
{

}

编辑:如果您没有将K指定为一个类并具有无参数构造函数,则会出现编译时错误:类型U必须是引用类型,并且需要具有无参数构造函数。


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