C#泛型是如何实现的? [英] How are C# Generics implemented?

查看:36
本文介绍了C#泛型是如何实现的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I had thought that Generics in C# were implemented such that a new class/method/what-have-you was generated, either at run-time or compile-time, when a new generic type was used, similar to C++ templates (which I've never actually looked into and I very well could be wrong, about which I'd gladly accept correction).

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

But in my coding I came up with an exact counterexample:

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();
        }
    }
}

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

Both of those console lines print.

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

I know that the actual reason this happens is that the virtual call to Object.GetHashCode() doesn't resolve to Test.GetHashCode() because the method in Test is marked as new rather than override. Therefore, I know if I used "override" rather than "new" on Test.GetHashCode() then the return of 0 would polymorphically override the method GetHashCode in object and this wouldn't be true, but according to my (previous) understanding of C# generics it wouldn't have mattered because every instance of T would have been replaced with Test, and thus the method call would have statically (or at generic resolution time) been resolved to the "new" method.

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

So my question is this: How are generics implemented in C#? I don't know CIL bytecode, but I do know Java bytecode so I understand how Object-oriented CLI languages work at a low level. Feel free to explain at that level.

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

As an aside, I thought C# generics were implemented that way because everyone always calls the generic system in C# "True Generics," compared to the type-erasure system of Java.

推荐答案

GenericTest<T>.Get(T) 中,C# 编译器已经选择了 object.GetHashCode 应该被调用(虚拟地).这不可能解决新"问题.GetHashCode 方法在运行时(在方法表中有自己的槽,而不是覆盖 object.GetHashCode 的槽).

In GenericTest<T>.Get(T), the C# compiler has already picked that object.GetHashCode should be called (virtually). There's no way this will resolve to the "new" GetHashCode method at runtime (which will have its own slot in the method-table, rather than overriding the slot for object.GetHashCode).

来自 Eric Lippert 的 有什么区别,第一部分:泛型不是模板,解释了问题(使用的设置略有不同,但课程很好地转化为您的场景):

From Eric Lippert's What's the difference, part one: Generics are not templates, the issue is explained (the setup used is slightly different, but the lessons translate well to your scenario):

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

This illustrates that generics in C# are not like templates in C++. You can think of templates as a fancy-pants search-and-replace mechanism.[...] That’s not how generic types work; generic types are, well, generic. We do the overload resolution once and bake in the result. [...] The IL we’ve generated for the generic type already has the method its going to call picked out. The jitter does not say "well, I happen to know that if we asked the C# compiler to execute right now with this additional information then it would have picked a different overload. Let me rewrite the generated code to ignore the code that the C# compiler originally generated..." The jitter knows nothing about the rules of C#.

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

And a workaround for your desired semantics:

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

Now, if you do want overload resolution to be re-executed at runtime based on the runtime types of the arguments, we can do that for you; that’s what the new "dynamic" feature does in C# 4.0. Just replace "object" with "dynamic" and when you make a call involving that object, we’ll run the overload resolution algorithm at runtime and dynamically spit code that calls the method that the compiler would have picked, had it known all the runtime types at compile time.

这篇关于C#泛型是如何实现的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆