C# 泛型约束传播

5
这个示例是真实问题的简化版本,但我该如何使其编译通过?我期望泛型约束会传播。
由于T是一个TClass,而TClass是一个类,为什么T不是一个类呢?
public class MyClass<TClass> where TClass : class 
{
    public void FuncA<Ta>() where Ta : class
    {
    }

    public void FuncB<Tb>() where Tb : TClass
    {
    }

    public void Func<T>()
        where T : TClass
    {
        FuncA<T>();
        FuncB<T>();
    }
}

编辑:

这实际上是有效的。Eric Lippert 让我思考,非常感谢。

由于 T 是 TClass,而 TClass 是 TAnotherType,因此 T 实际上是 TAnotherType。

public class MyClass<TClass, TAnotherType> where TClass : TAnotherType
{
    public void FuncA<Ta>() where Ta : TClass
    {
    }

    public void FuncB<Tb>() where Tb : TAnotherType
    {
    }

    public void Func<T>()
        where T : TClass
    {
        FuncA<T>();
        FuncB<T>();
    }
}
6个回答

14

由于T是一个TClass,而TClass是一个类,为什么T不是一个类呢?

前提1:"Bob Smith"是一个合法的名字。

前提2:"一个合法的名字"是一个三个词的句子片段。

结论:因此,"Bob Smith"是一个三个词的句子片段。

这个逻辑显然是不正确的。

前提1:T是一个TClass

前提2:TClass是一个类

结论:因此T是一个类。

出现错误的原因在于两个前提中使用了"是"这个词汇,但其含义完全不同。在第一个前提中,“是”表示“这两个事物之间具有is-a-kind-of 关系”,而在第二个前提中,“是”表示“这个事物具有特定的特征”。

可以直接替换TTClass来看出逻辑是否正确:

前提1:int是一个System.ValueType

(‘是’ 表示 ‘存在子类关系’)

前提2:System.ValueType是一个类

(‘是’ 表示 ‘具有特定属性,即可以通过引用进行复制’)

结论:因此int是一个类。

同样,我们得出了不正确的结论,因为“是”在两个前提中的意义不一致。

另一个例子:

前提1:汉堡比什么都没有要好。

前提2:什么都没有比一份好牛排要好。

结论:汉堡比一份好牛排要好,按传递性算。

最后,关于这个话题的更多想法,请参阅我的最近文章:

http://ericlippert.com/2011/09/19/inheritance-and-representation/


2
如果是一個好漢堡,它可能會是的。交個廚師朋友,你會驚訝地發現漢堡可以有多好! :) - configurator
如果我们定义 TClass : class,我们就是在说 TClass 必须是引用类型。如果我们接着说 T : TClass,我们就是在说类型 T 必须派生/实现(或与传递给 TClass 的类型相同)。由于 TClass 受限于引用类型,为什么它不能将 T 推断为引用类型呢?你能举个例子说明这种情况不成立吗?还是我完全理解错了? - Rob
3
@Rob:我确实举了一个例子,说明这种情况并不总是成立。请再次阅读答案。为什么你认为从引用类型派生的类型必须是引用类型呢?继承共享成员。其他属性不会被继承。鸭嘴兽是一种动物,动物是一个六个字母的单词,因此鸭嘴兽也是一个六个字母的单词吗?我不这么认为。 - Eric Lippert
1
@MatteS:“是”和“存在”是同一个动词。您正在使用“存在”表示两个不同的含义。请停止模棱两可地使用该动词。“T被限制为派生自TClass的类型。TClass被限制为按引用复制的类型。”现在,不及物性是显而易见的;并没有要求从引用类型派生的类型也必须按引用复制;派生不会保留按引用复制属性,就像它不保留其他属性(例如名称长度)一样。 - Eric Lippert
@configurator 如果你交了一個會給你好漢堡但不會做好牛排的廚師為朋友,那麼這個友誼或者這位廚師並沒有你想像中那麼好。;) - Jon Hanna
显示剩余3条评论

6
问题在于 T 不一定是引用类型。例如:
MyClass<IFormattable> foo = new MyClass<IFormattable>();
MyClass.Func<int>();

这段代码尝试调用 FuncA<int>,但是 int 没有遵守 : class 约束。

因此,你需要将 Func<T> 也加上 : class 约束:

public void Func<T>() where T : class, TClass

public void Func<T>() where T : TClass, class 这段代码无法编译,应该交换 classTClass 的位置。 - Saeed Amiri
正如Saeed的回复所指出的那样,显然你必须在where T : class, TClass处进行操作。 - MatteS
1
@invisible:但是 int 实现了 IFormattable 接口。 - BoltClock
我猜这就是问题所在,如果约束传播,我会期望调用 MyClass.Func<int>(); 时无法编译,但显然它们并没有。 - MatteS
@MatteS:Func<int>() 的哪一部分无法编译? int 可转换为 IFromattable,这就是约束所需的全部。 - Jon Skeet
显示剩余2条评论

4
编译此代码的方法如下:
public void Func<T>()
    where T :class, TClass
{
    FuncA<T>();
    FuncB<T>();
}

因为FunA的输入只是一个普通类而不是特殊类。

谢谢!顺便说一句,有趣的是我不能做 where T : TClass, class。 - MatteS
1
@MatteS 正如我所写,你应该使用 T:class, TClass 而不是 T:TClass, class - Saeed Amiri

0

我不确定以下内容是你代码中的错别字还是你复制/粘贴修改(为了简化)中的错误:

public void FuncA<Ta>() where Ta : class //TClass instead of class?
{
}

他在询问为什么“T:TClass”约束条件不能满足“Ta:class”,而“TClass”本身就是一个“class”。 - BoltClock
其他人已经回答了这一部分,我想我可以尝试一下,也许只是一个打字错误。 - myermian

0

看起来你只是好奇为什么不能这样做。而且你对像这样的工作示例不感兴趣:

public class MyClass<TClass> where TClass : class
{
    public void FuncA<Ta>() where Ta : class
    {
    }

    public void FuncB<Tb>() where Tb : TClass
    {
    }

    public void Func()
    {
        FuncA<TClass>();
        FuncB<TClass>();
    }
}

0
你们都说 T:TClass 和 TClass:class 不意味着 T:class。 好的,那么如果我给你:

public MyClass<TClass> where TClass : class
{
     public void MyFunc<T>() where T : TClass
     {
         // who cares ?
     }
}

能否提供一个使用非类类型作为T的示例,因为我真的不理解T : TClass和TClass : class不意味着T : class。请不要试图用奇怪的事情来解释,请用一个例子来证明。


嗨,我发现这篇文章很有趣,也很奇怪。.net总是设计得很糟糕:D。 - Toto

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