当前位置: 首页 > 编程语言 > C#

如何实现虚泛型方法调用?如何分享

时间:2023-04-11 11:56:38 C#

实现虚泛型方法调用?我对CLR如何实现这样的调用很感兴趣:abstractclassA{publicabstractvoidFoo();}Aa=...a.Foo();//<===?此调用导致某种类型的哈希映射查找类型参数令牌作为键,编译泛型方法特化(一个用于所有引用类型,不同代码用于所有值类型)作为值?我没有找到这方面的确切信息,所以这个答案的大部分是基于2001年关于.Netgenerics的优秀论文(甚至在.Net1.0出现之前!),后续文章中的简短说明以及我的内容收集了有关SSCLIv.2.0的源代码(尽管我找不到调用虚拟泛型方法的确切代码)。让我们从简单开始:如何调用非泛型非虚拟方法?通过直接调用方法代码,编译后的代码包含直接地址。编译器从方法表中获取方法地址(见下一段)。能这么简单吗?嗯,差不多。方法是JITed的事实让它变得有点复杂:实际调用的是编译方法的代码,然后只执行它,如果它还没有被编译的话;或者它是一条直接调用编译代码的指令(如果它已经存在)。我会进一步忽略这个细节。现在,如何调用非泛型虚方法?类似于C++等语言中的多态性,可以从this指针(引用)访问方法表。每个派生类都有自己的方法表及其方法。因此,要调用虚方法,获取对此的引用(作为参数传入),从那里获取对方法表的引用,查看其中的正确条目(条目号对于特定函数是常量)和调用入口指向的代码。通过接口调用方法有点复杂,但我们现在不感兴趣。现在我们需要了解代码共享。如果类型参数中的引用类型对应于任何其他引用类型,则可以在具有完全相同值类型的同一方法的两个“实例”之间共享代码。因此,例如C.M()与C.M()C.M()共享代码,但不与C.M()共享代码。类型类型参数和方法类型参数之间没有区别。(2001年的原始文章提到当两个参数都是具有相同布局的结构时也可以共享代码,但我不确定在实际实现中是否如此。)让我们在泛型方法的过程中走一个中间步骤:泛型类型中的非泛型方法。由于代码共享,我们需要从某个地方获取类型参数(例如,用于调用代码,如newT[])。出于这个原因,泛型类型(例如C和C++)的每个实例都有自己的类型句柄,其中包含类型参数和方法表。普通方法可以使用此引用访问此类型句柄(技术上称为MethodTable结构,尽管它包含的不仅仅是一个方法表,但在技术上容易混淆)。有两种类型的方法不能做到这一点:静态方法和值类型方法。对于那些,类型句柄作为隐藏参数传入。对于非虚拟泛型方法,类型句柄是不够的,所以他们得到一个不同的隐藏参数MethodDesc包含类型参数。此外,编译器无法将实例存储在普通方法表中,因为它是静态的。因此,它为泛型方法创建第二个不同的方法表,由类型参数索引,并从那里获取方法地址(如果兼容的类型参数已经存在)或创建一个新条目。虚拟泛型方法现在很简单:编译器不知道具体类型,所以它必须在运行时使用方法表。而普通的方法表是不能用的,所以必须到特殊方法表中查找泛型方法。当然,包括类型参数在内的隐藏参数仍然存在。我在研究这个问题时学到的一个有趣的花絮:因为JITer非常懒惰,下面的(完全没用的)代码有效:objectLift(intcount)whereT:new(){if(count==0)returnnewT();返回电梯>(计数-1);等效的C++代码会导致编译器放弃堆栈溢出。是的。特定类型的代码由CLR在运行时生成,并保留哈希表(或类似表)的实现。通过C#的CLR第372页:当对使用泛型类型参数的方法进行JIT编译时,CLR获取该方法的IL,替换指定的类型参数,然后创建特定于该方法的本机代码,该方法对指定的数据类型进行操作。这正是您想要的,也是泛型的主要特征之一。然而,这有一个缺点:CLR不断地为每个方法/类型组合生成本机代码。这称为代码爆炸。这最终可能会大大增加应用程序的工作集,从而损害性能。幸运的是,CLR内置了一些优化来减少代码爆炸。首先,如果为特定类型参数调用一个方法,随后又使用相同类型参数再次调用,则CLR只会为该方法/类型组合编译一次代码。因此,如果一个程序集使用List,而另一个完全不同的程序集(加载在同一个AppDomain中)也使用List,则CLR只会为List编译一次该方法。这大大减少了代码爆炸。编辑我现在遇到了https://msdn.microsoft.com/en-us/library/sbh15dya.aspx,它明确指出要使用引用类型泛型重用相同的代码,所以我会接受它作为权威机构。原始答案我在这里看到两个不同意的答案,都提到了他们的立场,所以我会试着加上我的两分钱。首先,由MicrosoftPress出版的JeffreyRichter的ClrviaC#与msdn博客一样有效,特别是因为该博客已经过时(有关他的更多书籍,请参见http://www.amazon.com/Jeffrey-Richter/e/B000APH134必须承认他是windows和.net方面的专家。现在我自己分析一下。显然,两个包含不同引用类型参数的泛型类型不能共享相同的代码。例如List和List>不能共享相同的,因为这将导致能够通过反射将TypeA的对象添加到列表中,并且clr也是遗传上的强类型(与Java不同,只有编译器验证泛型,但底层JVM没有关于它们的线索)。这不仅适用于类型,也适用于方法,因为例如类型T的泛型方法可以创建类型T的对象(例如,没有什么可以阻止它创建新的列表),在这种情况下重用相同的代码将导致严重损坏。此外,GetType方法不可重写,事实上它总是返回正确的泛型类型,证明每个类型参数确实有自己的代码。(这比看起来更重要,因为clr和jit都是基于创建类型对象,通过使用GetType(),这意味着对于每个类型参数,即使是引用类型,也必须有一个单独的对象)代码重用导致另一个问题,因为运算符将不再正常运行,并且所有类型的转换通常都会出现严重问题。现在进行实际测试:我通过使用包含静态成员的泛型类型来测试它,而不是创建具有不同类型参数的两个对象,并且静态字段不共享,显然即使对于引用类型,代码也不共享。编辑:有关操作方法,请参阅http://blogs.msdn.com/b/csharpfaq/archive/2004/03/12/how-do-c-generics-compare-to-c-templates.aspx:空间使用C++和C#的空间使用情况不同。因为C++模板是在编译时完成的,所以在模板中每次使用不同的类型都会导致编译器创建一个单独的代码块。在C#世界中它有点不同。使用特定类型的实际实现是在运行时创建的。当在运行时创建类似List的类型时,JIT将查看该类型是否已经创建。如果有,它只是用户编码的。如果不是,它会采用编译器生成的IL,并对实际类型进行适当的替换。这不太对。每个值类型都有一个单独的本机代码路径,但由于引用类型都是引用大小的,因此它们可以共享它们的实现。这意味着C#方法在磁盘和内存中的占用空间应该更小,因此这是泛型优于C++模板的优势。事实上,C++链接器实现了一个称为“模板折叠”的功能,链接器在其中查找本机代码的相同部分,如果找到它们,则将它们折叠在一起。所以看起来不是那么明确。可以看出CLR“可能”重用引用类型的实现,就像当前的c++编译器所做的那样,但这并不能保证,对于使用stackalloc和指针的不安全代码,情况可能并非如此,并且可能是相同的适用于其他情况。但是我们要知道,在CLR类型系统中,它们是被当作不同的类型来对待的,比如静态构造函数的不同调用,单独的静态字段,单独的类型对象,类型参数为T1的对象不应该访问类型为T1的对象parametersT2的另一个对象的私有字段(虽然对于同类型的对象,确实可以从同类型的另一个对象访问私有字段)。以上就是C#学习教程:如何实现虚泛型方法调用?如果所有分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: