【问题标题】:Why can't the type conversion of `this` be implicitly inferred from the generic contraint?为什么不能从泛型约束中隐式推断 `this` 的类型转换?
【发布时间】:2017-10-19 16:45:32
【问题描述】:

我有以下课程:

public class Item<TItem>
    where TItem : Item<TItem>
{
    void GetReference()
    {
        TItem item = this;
    }
}

此处TItem item = this; 生成编译器错误“无法将Item&lt;TItem&gt; 隐式转换为TItem”。

但是为什么我们需要在这里进行转换呢?我们已经定义了约束where TItem : Item&lt;TItem&gt;,所以可以认为根本不需要转换,因为这两种类型是相同的,不是吗?

顺便说一句,可以使用显式转换。这也在编译器错误中说明。

【问题讨论】:

  • 您为什么认为where 子句描述了TItem身份 及其约束?如果您说where TItem : Mammal,您会认为TItem 必须是完全是哺乳动物,还是认为TItem 必须是Mammal 兼容的任何类型,比如TigerDog?我很想知道人们是如何对编程语言产生错误信念的;你认为where 子句是什么意思,你是怎么知道的?
  • 我看着它想了想:“嗯,TItem 可以是Item&lt;TItem&gt; 或任何派生出来的东西。所以由于我们目前在Item&lt;TItem&gt; 类中工作,所以这个类(@ 的类型987654339@) 应该属于该类别,因此应该可以分配给TItem。”就这样,乔恩·斯基特(Jon Skeet)所描绘的案例悄悄地从我的脑海中溜走了。
  • 知道了;谢谢!如果你对这个主题感兴趣,几年前我写了一篇关于它的文章。 blogs.msdn.microsoft.com/ericlippert/2011/02/03/… 我建议避免这种模式;正如您所发现的,这很难推理。
  • 我已经阅读了我的评论几次,现在我犯的错误对我来说更清楚了。仍然很难就这个概念接受一些培训,因为它很难形象化。

标签: c# generics types constraints implicit


【解决方案1】:

因为它不安全。考虑:

public class GoodItem : Item<GoodItem>
{
    // No problem
}

public class EvilItem : Item<GoodItem>
{
    // GetReference body would be equivalent to 
    // GoodItem item = this;
    // ... but this *isn't* a GoodItem, it's an EvilItem!
}

EvilItem 满足 TItem 的约束没有问题 - GoodItem 确实派生自 Item&lt;GoodItem&gt;

没有办法表达被声明的和类型参数之间的关系,而这正是你真正想要的。

【讨论】:

    【解决方案2】:

    那是因为你的类是 Item&lt;TItem&gt; 而不是 TItem。你可以有;

    Item<TItem> item = this;
    

    这个样本有点让人费解。将其置于上下文中TItem item = this; 决定尝试这样做

    Item<TItem> item = new Item<Item<TItem>>();
    

    【讨论】:

    • 但是TItem 必须是Item&lt;TItem&gt; 对吧?既然禁忌告诉它
    • @BrunoZell 我认为您对泛型所说的内容感到困惑。如果你想要一个TItem 的实例,你可以拥有Activator.CreateInstance(typeof(TItem)),但该类是它自己的类型,withTItem 类型的泛型,但它本身不是TItem。这是Item&lt;TItem&gt;
    • @DiskJunky 他在做Curiously recurring template pattern,通用约束说"TItem 必须是或继承自Item&lt;TItem&gt;"this 是@987654337 @ 所以它应该是可分配的。
    • @BrunoZell 您可以将任何东西转换为任何东西,它会编译。这只是将问题推迟到运行时。
    • @ScottChamberlain 约束说TItem 可以转换为Item&lt;TItem&gt;,它Item&lt;TItem&gt; 可以转换为TItem。想象一下,如果约束说where TItem : object。仅仅因为您知道TItem 是一个对象并不意味着所有对象都是TItems。
    【解决方案3】:

    因为每个TItem 都是Item&lt;TItem&gt;(由 where 约束声明),但反之则不然。

    TItem 可能比 Item&lt;TItem&gt; 更派生,this 也是如此,TItem 可能是 Apple,this 可能是橙子。所以编译器会阻止赋值。

    目前在c#中没有办法声明类型参数必须匹配继承类本身的类型。

    有两种常见的方法来解决这个问题。首先使用显式强制转换

    TItem GetReference() => (TItem) this;
    

    确保继承类使用正确的类型参数是您的工作,否则如果您尝试使用此方法,您可能会遇到运行时异常。

    第二种方法是使用类本身的返回类型。这是安全的(没有运行时例外),但对派生类没有任何形式的契约。也就是说,您应该确保为每个派生类编写此方法。

    Item<TItem> GetReference() => this;
    

    现在您可以在派生类中隐藏此方法。

    new Derived GetReference() => this; // public class Derived : Item<Derived>
    

    请注意,此功能已在 GitHub c# 存储库中请求,https://github.com/dotnet/csharplang/issues/252

    您只需要等待 c# 团队添加此功能 :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-22
      • 1970-01-01
      • 1970-01-01
      • 2013-06-30
      • 2019-10-13
      • 1970-01-01
      • 2022-11-23
      相关资源
      最近更新 更多