为什么使用继承类型的泛型类没有检测到类约束?

6

问问题的措辞很难,希望下面的代码片段能让事情变得清晰明了:

public class DemoClass<TBase> where TBase : class
{
    public void DemoMethod<T>(T target) where T : TBase
    {
        //The following line causes a design-time error: Type argument 'T' does not satisfy the 'Class' constraint for type parameter 'T'.
        WeakReference<T> demoRef = new WeakReference<T>(target);
    }
}
WeakReference需要满足class约束的类型T。到目前为止,一切都很好,但是......

为什么编译器无法检测到实际上T确实满足 T : TBase : class,因为(实际上)?


1
继承的类型在哪里?TBase只是一个参数,保证是引用类型,它没有具体的类型。 - Selman Genç
@SelmanGenç T 被限制为类型 TBase,而 TBase 本身被限制为类类型。因此,T 本身应该被传递性地限制为类类型。 - poke
@poke 这并不是普遍的真实情况。任何 ValueType 都继承自 object,但它绝对不符合 class - InBetween
@InBetween 在那条评论中,我只是解释了OP提出这个问题的原因(这也对我有意义)。但是,正如我在下面的答案中解释的那样,我知道实际情况并非如此。 - poke
2个回答

5
为什么编译器无法检测到T实际上确实满足(T : TBase : class)的条件呢?
因为这不是真的。除了Poke在他的答案中提到的内容,这也是非法的,因为所有值类型都继承自object
 var dc = new DemoClass<object>();
 dc.DemoMethod(1); //woops, just attempted to create a WeakReference<int>

当涉及值类型时,你的推理就会崩溃。虚构的?是的,但完全合法,所以编译器别无选择,必须将你的代码视为非法。
更新
针对Jon Hana在下面的评论中提到的问题,即在上面的代码中T实际上不是int,而是object,并且1被隐式装箱,这绝对不是真的。考虑以下DemoMethod的变体:
public T DemoMethod<T>(T target) where T : TBase
{
    return target;
}

以下是代码:

var dc = new DemoClass<object>();
var i = dc.DemoMethod(1);

iint 类型,而不是 object。此外,以下代码将正确执行:

long i = dc.DemoMethod(1);

这也证明了 T 不能是一个装箱的 int,因为隐式转换在运行时会失败;你不能将值类型拆箱为除其本身之外的任何类型。
当然,你也可以始终明确设置 T,这也可以正常编译:
dc.DemoMethod<int>(1);

@JonHanna 这不是真的,如果你在方法内打印出 typeof(T),你会看到它是 int - poke
@JonHanna,那绝对不是真的,请看我回答中的更新。 - InBetween

4

让我们来查看文档,了解 T : class 到底是什么意思:

where T : class

类型参数必须是引用类型;这同样适用于任何类、接口、委托或数组类型。

不幸的是,如果 T 是一个接口,这个限制已经被满足了。因此,你可以构造一个简单的示例,证明应用 T : class 转移性将不起作用:

public interface ITest { }
public struct Test : ITest { }

如果您现在创建一个 DemoClass<ITest>,那么您就满足了类型约束,因为此处的 ITest 是一个“类”。但是当您调用方法 DemoMethod<Test> 时,尽管 Test 继承自 ITest,但您没有 T 的引用类型。
总体而言,这些特殊的泛型类型约束并不遵循继承规则。这就是为什么它们被单独定义并且还没有被类型系统所建立。它们存在的特殊语法是因为类型系统无法以其他方式表达这些限制。

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