【问题标题】:The rules of generics and type constraints泛型规则和类型约束
【发布时间】:2013-02-15 22:19:32
【问题描述】:

出于好奇,为什么编译器对待不受约束的泛型类型与 typeof(object) 有任何不同?

class Bar { }

class Foo
{
    void foo(object thing)
    {
        ((Bar)thing).ToString();
    }
}

class Foo<T>
{
    void foo(T thing)
    {
        ((Bar)thing).ToString();
    }
}

在上面,将“T thing”转换为 Bar 会导致编译器错误。然而,将“对象事物”转换为 Bar 是编译器允许我做的事情,当然风险自负。

我不明白为什么。毕竟在 .net 中的对象是一个包罗万象的对象,运行时类型可以是装箱的值或任何类型的对象。所以我看不出编译器有什么逻辑理由来区分这两种情况。我能做的最好的事情是“程序员希望编译器对泛型类型进行类型检查,而不是对对象进行类型检查”。 :) 仅此而已吗?

顺便说一句,我知道我仍然可以在 Foo 案例中完成我的演员表,只需编写

((Bar)(object)thing).ToString();

我只是想了解编译器为什么会这样......

【问题讨论】:

  • int 转换为Bar 在编译时是否合法?当您使用int 填写该类型参数时,它是否应该然后 开始出现编译器错误?如果组件不是你的,所以你看不到问题怎么办? T 不是对象。这是非常具体的事情。
  • 您是否也知道您可以说class Foo&lt;T&gt; where T : Bar确保 T 始终可以转换为Bar
  • 我确信 Eric Lippert 在某处有一篇关于此的博文,但我找不到...
  • 您遇到的问题是您认为 T 意味着任何东西,而它实际上意味着非常具体的东西,但尚未指定。
  • 由于将装箱值类型转换为任何引用类型都会导致错误,因此真正的问题肯定是编译器没有(通用)方法来区分装箱值类型和引用类型;因为如果可以的话,它肯定会为来自object 的此类转换引发编译时错误,即使在没有泛型的情况下也是如此。

标签: c# .net generics


【解决方案1】:

这里的意义是object。如果第一个示例是 object 以外的任何内容,则其行为将相同。基本上,你现在在这里说的是:

(Bar)thing

is: "将T 转换为Bar";在一般情况下,这远不合法。通过添加object 即可:

(Bar)(object)thing

这是“将T 转换为object...” - 这始终是合法的,因为object 是所有托管类型的根;并注意这可能涉及一个框 - “......然后将object 转换为Bar” - 再次;它在编译时总是合法的,并且在运行时涉及类型检查(“unbox-any”)。

例如:假设TDateTime...

DateTime thing = ...
Bar bar = (Bar)(object)thing;

完全有效;确定它会在运行时失败,但是:这是您需要牢记的场景。

【讨论】:

  • 虽然正确,但我认为 OP 要求选择背后的设计决策,以便在 compile 时间为后一种情况但在 run 前者的时间(假设事物不能转换为Bar
  • 你们都是(@Marc,@Ron)。我认为这是一个很好的解释。将对象转换为某些东西总是向下转换,更具体的东西,但一般情况可以是任何一种,因此 constructed 类型不会为任何 T 编译 - 这是给出编译的一个很好的理由-时间错误!
【解决方案2】:

它归结为创建泛型的语义和目的。如果你有一个通用类型 T,编译器不会让你随意将它直接强制转换为任何其他对象。这是有道理的,因为 T 的目的是强制程序员指定 T 实际上是什么类型。它不会是“对象”,而是特定类型的对象。在编译时,编译器无法知道 T 中的内容,因此无法强制转换。

从对象进行强制转换是因为它是一个匿名对象 - 与在其使用中定义的 KNOWN 对象类型相反。

这可以使用“where”子句进行扩展。例如,您可以指定 T 必须是 IBar 类型;

interface IBar { }

class Bar : IBar { }

class Foo<T>
    where T : IBar
{
    void foo(T thing)
    {
        ((IBar)thing).ToString();
    }
}

继承也适用于 where 子句;

class Bar { }

class Foo<T>
    where T : Bar
{
    void foo(T thing)
    {
        // now you don't need to cast at all as the compiler knows
        // exactly what type T is (at a parent level at least)
        thing.ToString();
    }
}

【讨论】:

  • 我很高兴将此标记为答案,尽管我发现您的推理与我最初的建议差不多;它确实归结为“用户”(我的通用类的用户,或者更确切地说是定义了 T 的构造类)的期望。要说“T 不仅仅是一个对象,它是特定的东西”,恕我直言,是多余的。任何对象都可以并且通常是非常具体的东西。并且有不同的方式来查看同一个对象。我可能想要支持任何类型,但如果类型是我知道的类型,则以特定方式表现,例如 - 泛型类与否。
  • @TheDag 根据类型 T 更改泛型的行为通常不是一个好主意,尽管存在处理特定类型的有效案例,例如装箱或未装箱类型。在您的示例中,您尝试直接从 T 转到默认情况下几乎肯定会是特定类型到另一个特定类型 Bar。当没有关系或通用接口时,这将尝试做(Foo)Bar。我仍然认为编译器警告是有效的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多