草庐IT

c# - NET公共(public)语言运行时的泛型实现是什么

coder 2024-05-24 原文

当您在C(或一般的.NET)中使用泛型集合时,编译器是否基本上完成了开发人员过去为特定类型生成泛型集合所必须做的腿部工作。所以基本上。…它只是帮我们节省了工作?
现在我想,这不可能是正确的。因为没有泛型,所以我们不得不在内部使用非通用数组来进行集合,因此存在装箱和拆箱(如果是值类型的集合)等。
那么,泛型在cil中是如何呈现的呢?当我们说我们想要一个通用的东西集合时,它在做什么呢?我不一定需要cil代码示例(尽管这没关系),我想知道编译器如何获取泛型集合并呈现它们的概念。
谢谢!
我知道我可以用ildasm来看待这个问题,但在我看来,我仍然是中国人,我还没有准备好解决这个问题。我只想知道c(以及其他我猜也是的语言)如何在cil中呈现的概念来处理泛型。

最佳答案

请原谅我冗长的帖子,但这个话题相当宽泛。我将尝试描述C编译器发出什么,以及JIT编译器在运行时如何解释它。
ECMA-335(这是一个写得非常好的设计文档;请查看)是了解.net程序集中如何表示所有内容(我是说所有内容)的最佳位置。程序集中有几个相关的cli元数据表可用于获取常规信息:
genericparam-存储有关泛型参数(索引、标志、名称、所属类型/方法)的信息。
genericparamconstraint-存储有关泛型参数约束(拥有泛型参数、约束类型)的信息。
methodspec-存储实例化的泛型方法签名(例如bar.method表示bar.method)。
typespec-存储实例化的泛型类型签名(例如bar表示bar)。
因此,考虑到这一点,让我们通过一个简单的例子来使用这个类:

class Foo<T>
{
    public T SomeProperty { get; set; }
}

当c编译器编译这个示例时,它将在typedef元数据表中定义foo,就像对任何其他类型一样。与非泛型类型不同,它还将在genericparam表中有一个描述其泛型参数(index=0,flags=?,name=(索引到字符串堆“t”),owner=类型“foo”)。
typedef表中的数据列之一是methoddef表的起始索引,methoddef表是在此类型上定义的方法的连续列表。对于foo,我们定义了三个方法:someproperty的getter和setter以及编译器提供的默认构造函数。因此,methoddef表将为每个方法保存一行。methoddef表中的一个重要列是“signature”列。此列存储对一个字节块的引用,该字节块描述方法的确切签名。ECMA-335详细介绍了这些元数据签名blob,因此我不会在这里反刍这些信息。
方法签名blob包含有关参数和返回值的类型信息。在我们的例子中,setter取t,getter返回t。那么,t是什么?在签名blob中,它将是一个特殊值,表示“索引0处的泛型类型参数”。这意味着genericparams表中的行的index=0,owner=type“foo”,这是我们的“t”。
auto property backing store字段也是如此。typedef表中的foo条目将有一个字段表的起始索引,字段表有一个“signature”列。字段的签名将表示字段的类型是“索引0处的泛型类型参数”。
这一切都很好,但是当t是不同类型时,代码生成在哪里发挥作用呢?实际上,jit编译器的责任是为泛型实例化而不是c编译器生成代码。
我们来看一个例子:
Foo<int> f1 = new Foo<int>(); 
f1.SomeProperty = 10;
Foo<string> f2 = new Foo<string>();
f2.SomeProperty = "hello";

这将编译成如下CIL:
newobj <MemberRefToken1> // new Foo<int>()
stloc.0 // Store in local "f1"
ldloc.0 // Load local "f1"
ldc.i4.s 10 // Load a constant 32-bit integer with value 10
callvirt <MemberRefToken2> // Call f1.set_SomeProperty(10)
newobj <MemberRefToken3> // new Foo<string>()
stloc.1 // Store in local "f2"
ldloc.1 // Load local "f2"
ldstr <StringToken> // Load "hello" (which is in the user string heap)
callvirt <MemberRefToken4> // Call f2.set_SomeProperty("hello")

那么MemberRefToken的业务是什么?MemberRefToken是引用MemberRef元数据表中的行的元数据标记(标记是四个字节的值,其中最重要的字节是元数据表标识符,其余三个字节是行号,基于1)。此表存储对方法或字段的引用。在泛型之前,这是一个表,用于存储在引用程序集中定义的类型中使用的方法/字段的信息。但是,它也可以用于引用一般实例化中的成员。所以假设memberRefToken1引用memberRef表中的第一行。它可能包含以下数据:class=typespectoken1,name=“.ctor”,blob=
typespectoken1将引用typespec表中的第一行。从上面我们知道这个表存储泛型类型的实例化。在这种情况下,此行将包含对“foo”的签名blob的引用。所以这个memberRefToken1实际上是说我们在引用“foo.ctor()”。
MemberRefToken1和MemberRefToken2将共享相同的类值,即TypeSpecToken1。但是,它们在名称和签名blob上会有所不同(methodreftoken2代表“set_someproperty”)。同样,memberreftoken3和memberreftoken4将共享typespectoken2,即“foo”的实例化,但在名称和blob上的差别相同。
当jit编译器编译上述cil时,它注意到它看到了一个以前从未见过的泛型实例化(即foo或foo)。接下来发生的事情被希夫·库马尔的回答很好地覆盖了,所以我不会在这里详细重复。简单地说,当jit编译器遇到一个新的实例化泛型类型时,它可能会使用实例化中的实际类型代替泛型参数,通过字段布局向其类型系统发出一个全新的类型。它们还将拥有自己的方法表,而每个方法的jit编译将涉及到用实例中的实际类型替换对泛型参数的引用。jit编译器还负责强制cil的正确性和可验证性。
所以总结一下:c编译器发出元数据,描述什么是泛型以及泛型类型/方法是如何实例化的。jit编译器使用此信息在运行时为实例化的泛型类型发出新类型(假设它与现有的实例化不兼容),并且每个类型都有自己的代码副本,这些代码是基于实例化中使用的实际类型进行jit编译的。
希望这在某种程度上是有意义的。

关于c# - NET公共(public)语言运行时的泛型实现是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5099977/

有关c# - NET公共(public)语言运行时的泛型实现是什么的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  3. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  4. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  5. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  6. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  7. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  8. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  9. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  10. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

随机推荐