【问题标题】:How are C# Generics implemented?C#泛型是如何实现的?
【发布时间】:2012-07-11 16:06:54
【问题描述】:

我曾认为 C# 中的泛型是这样实现的,即在运行时或编译时生成一个新的类/方法/what-have-you,当使用新的泛型类型时,类似于 C++ 模板(我从未真正研究过,我很可能是错的,对此我很乐意接受更正)。

但在我的编码中,我想出了一个确切的反例:

static class Program {
    static void Main()
    {
        Test testVar = new Test();

        GenericTest<Test> genericTest = new GenericTest<Test>();
        int gen = genericTest.Get(testVar);

        RegularTest regTest = new RegularTest();
        int reg = regTest.Get(testVar);

        if (gen == ((object)testVar).GetHashCode())
        {
            Console.WriteLine("Got Object's hashcode from GenericTest!");
        }
        if (reg == testVar.GetHashCode())
        {
            Console.WriteLine("Got Test's hashcode from RegularTest!");
        }
    }

    class Test
    {
        public new int GetHashCode()
        {
            return 0;
        }
    }

    class GenericTest<T>
    {
        public int Get(T obj)
        {
            return obj.GetHashCode();
        }
    }

    class RegularTest
    {
        public int Get(Test obj)
        {
            return obj.GetHashCode();
        }
    }
}

这两条控制台线都会打印。

我知道发生这种情况的实际原因是对 Object.GetHashCode() 的虚拟调用没有解析为 Test.GetHashCode() 因为 Test 中的方法被标记为新方法而不是覆盖。因此,我知道如果我在 Test.GetHashCode() 上使用“覆盖”而不是“新”,那么返回 0 将多态地覆盖对象中的 GetHashCode 方法,这不是真的,但根据我(以前)的理解对于 C# 泛型,这无关紧要,因为 T 的每个实例都将被替换为 Test,因此方法调用将静态(或在泛型解析时)被解析为“新”方法。

所以我的问题是这样的:泛型是如何在 C# 中实现的?我不知道 CIL 字节码,但我知道 Java 字节码,所以我了解面向对象的 CLI 语言是如何工作的等级。随意在该级别进行解释。

顺便说一句,我认为 C# 泛型是这样实现的,因为与 Java 的类型擦除系统相比,每个人都将 C# 中的泛型系统称为“真正的泛型”。

【问题讨论】:

  • 有什么理由在这里转换为对象 'gen == ((object)testVar).GetHashCode()' ?
  • 虽然它没有直接回答您的问题,但blogs.msdn.com/b/ericlippert/archive/2012/07/10/… 提供了一些关于泛型如何强制转换以及它们在 C# 中如何相互关联的有用信息。
  • @Yogendra 这样做会访问 Object.GetHashCode() 方法,而不是“新”方法 Test.GetHashCode()。这就是它返回不同值的原因(因为它完全运行不同的方法)。

标签: c# generics polymorphism overriding


【解决方案1】:

GenericTest&lt;T&gt;.Get(T) 中,C# 编译器已经选择应该(实际上)调用object.GetHashCode。这不可能在运行时解析为“新”GetHashCode 方法(它将在方法表中有自己的槽,而不是覆盖object.GetHashCode 的槽)。

来自 Eric Lippert 的 What's the difference, part one: Generics are not templates,对该问题进行了解释(使用的设置略有不同,但课程很好地适用于您的场景):

这说明 C# 中的泛型与 C++ 中的模板不同。 您可以将模板视为花哨的搜索和替换 机制。[...] 这不是泛型类型的工作方式;泛型类型是, 好吧,通用。我们执行重载决议一次并在 结果。 [...] 我们为泛型类型生成的 IL 已经有了 它要调用的方法被挑选出来。抖动不说 “好吧,我碰巧知道如果我们要求 C# 编译器执行 现在有了这些附加信息,它就会选择一个 不同的过载。让我重写生成的代码以忽略 C# 编译器最初生成的代码......” jitter 知道 与 C# 的规则无关。

以及您所需语义的解决方法:

现在,如果您确实希望在运行时根据运行时类型重新执行重载决议 论据,我们可以为您做到;这就是新的“动态” C# 4.0 中的功能。只需将“对象”替换为“动态”以及何时 您进行涉及该对象的调用,我们将运行重载 运行时解析算法和动态吐出调用的代码 编译器会选择的方法,如果它知道所有 编译时的运行时类型。

【讨论】:

  • 啊,埃里克,没有你我们该怎么办。
  • 是的,我在以“我知道发生这种情况的真正原因是……”开头的段落中说过,我的问题是“C# 泛型是如何实现的?”我会阅读您发送的链接,也许这会回答我的问题。
  • @您的编辑:这不是我想这样做:我来自 Java 背景,那里不存在整个“新方法”的考验。我认为它很容易出现用户错误,我不打算故意使用它。我遇到它的原因是因为我正在编写一个抽象类,我想强制子类实现 GetHashCode,所以我写了“public abstract int GetHashcode();”,没有意识到为了让任何调用 GetHashCode 的东西都能工作一般来说,我不得不说“public override abstract int GetHashcode();”,这对我的口味来说非常冗长。
  • 我刚刚学到了在互操作之外使用dynamic 的正当理由。众神之恩!
猜你喜欢
  • 1970-01-01
  • 2011-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
相关资源
最近更新 更多