简体   繁体   English

CLR在调用struct的方法时如何工作

[英]How CLR works when invoking a method of a struct

I think I've known the answer for a class, just want to confirm my understanding is correct. 我想我已经知道了一堂课的答案,只想确认我的理解是正确的。 Let's say I have a ClassA and its instance named a . 假设我有一个ClassA ,它的实例名为a When a.MethodA() is invoked: 当调用a.MethodA()

(1) CLR find the type of ClassA by the type pointer of a in the heap(the type have been loaded into the heap) (1)CLR通过堆中a类型指针找到ClassA类型 (该类型已加载到堆中)

(2) Find the MethodA in the type, if not found, go to its base type, until the object class. (2)在类型中找到MethodA ,如果找不到,则转到其基类型,直到object类。

Maybe my understanding is not quite precise, but I think it's basicly correct(Correct me if it's wrong!). 也许我的理解不太准确,但我认为这是基本正确的(纠正我,如果它是错的!)。 And here comes the question of a simple struct . 这是一个简单结构的问题。

struct MyStruct
{
   public void MethodA() { }
}

I have var x = new MyStruct(); 我有var x = new MyStruct(); , its value is on the stack, and the type of MyStruct has been loaded into the heap. ,它的值在堆栈上,并且MyStruct的类型已经加载到堆中。 When execute x.MethodA() , of course no boxing. 当执行x.MethodA() ,当然没有拳击。 How CLR find MethodA and get the IL and execute/JIT it? CLR如何找到MethodA并获取IL并执行/ JIT呢? I think the answer is probably:(again, correct me if I'm wrong) 我想答案可能是:(再次,如果我错了,请纠正我)

(1) we have the declaring type of x on the stack. (1)我们在堆栈上有x声明类型 CLR find its type by the info on the stack, and find MethodA in its type. CLR通过堆栈上的信息找到它的类型,并在其类型中找到MethodA -- let's call it assumptionA . - 让我们称之为assumptionA

I'll be happy if you tell me my assumptionA is correct. 如果你告诉我我的assumptionA是正确的,我会很高兴的。 But even it's wrong, it tells a truth: CLR has a way to find a struct's type without boxing. 但即使它是错的,它也说明了一个事实:CLR有一种方法可以在没有装箱的情况下找到结构类型。

Now what about x.ToString() or x.GetType() ? 那么x.ToString()x.GetType()呢? We know that the value will be boxed, and then it will perform like a class. 我们知道该值将被加框,然后它将像一个类一样执行。 But why do we need boxing here? 但为什么我们需要拳击呢? Since we can get its type(assumptionA tells us), why not go to its base type and find the method(just like a class)? 既然我们可以得到它的类型(假设A告诉我们),为什么不去它的基类型并找到方法(就像一个类)? Why need an expensive box operations here? 为什么这里需要昂贵的箱子操作?

Well, there's a few different things going on here: 嗯,这里有一些不同的事情:

  • For methods that are defined in the struct , the CLR just takes a look at the type definition in the assembly metadata when it's being loaded in order to figure out what the methods are, and when a method Foo calls MethodA , the CLR just binds to the correct method when MethodA is JIT'd . 对于结构定义的方法,CLR只是在加载时查看程序集元数据中的类型定义,以便弄清楚方法是什么,当方法Foo调用MethodA ,CLR只是绑定到MethodA是JIT时的正确方法。 There's nothing else actually happening after the compilation has all taken place; 编译完全发生后,没有其他任何事情发生; the method is called directly, because any information that's needed is already present. 直接调用该方法,因为已经存在所需的任何信息。

  • For virtual inherited struct methods like ToString , there has to be boxing because virtual calls can only be called on Object s, by design -- without boxing, there's no v-table to look into in order to figure out the resulting method. 对于像ToString这样的虚拟 继承 结构方法, 必须进行装箱,因为虚拟调用只能在 Object上调用,而不是装箱,没有v-table可以查看以找出生成的方法。 (The fact that the method call might be immediately after the boxing might allow for potential optimizations, but it's a long shot -- I doubt the JIT compiler does this.) apparently there's no boxing; (方法调用可能在拳击之后立即进行,这 可能会允许潜在的优化,但这是一个长镜头 - 我怀疑JIT编译器是否这样做。) 显然没有拳击; I was wrong because I didn't notice that these methods are overridden. 我错了,因为我没有注意到这些方法被覆盖了。 Indeed, for overridden methods, the compiler does perform the optimization by just calling the method directly, because there's no reason not to. 事实上,被替代的方法,该编译器仅通过直接调用方法进行优化,因为没有理由不这样做。 (There are no virtual methods for value types that are not overridden, so that's not actually an issue here.) (对于未被覆盖的值类型, 没有虚拟方法,因此这里实际上不是问题。)

  • For non-virtual struct methods that are inherited , the object needs to be boxed simply because the method is, by definition, being called on a reference type, not on a value type; 对于继承的 非虚拟结构方法,需要将对象简单地加框,因为根据定义,该方法是在引用类型上调用,而不是在值类型上调用; there's no need to special-case this in the compiler, because I believe the JIT compiler can actually do optimizations (like avoiding boxing) when it's JIT'ing a method like GetType (though someone please correct me if I'm wrong about this optimization thing). 没有必要在编译器中使用特殊情况,因为我相信JIT编译器实际上可以进行优化(比如避免装箱)当JIT采用类似GetType的方法时(尽管如果我对这个优化有误,请有人纠正我事情)。

AssumptionA is wrong. AssumptionA错了。 The C# compiler's symbol table stores type information. C#编译器的符号表存储类型信息。 That static type information is used in nearly all cases, the dynamic type stored in an object is only needed during type checks ( is operator), casting ( as operator and actual cast syntax), and array variance, and then only when the dynamic type isn't known to the compiler. 静态类型信息在几乎所有情况下使用的,存储在对象的动态类型中的类型检查时,才需要( is运营商),铸造( as操作者和实际铸造语法)和阵列方差,然后仅当动态型编译器不知道。 The dynamic type of an unboxed struct is always statically known, and dynamic type of a class instance is statically known near the instantiation and inside a conditional block which performed a type check (eg in if (x is T) y = (T)x; the type is known inside the then-part, so the cast doesn't require another dynamic check). 未装箱结构的动态类型始终是静态已知的,并且类实例的动态类型在实例化附近和执行类型检查的条件块内是静态已知的(例如,在if (x is T) y = (T)x;类型在then-part中是已知的,因此强制转换不需要另外的动态检查)。

Ok, now because the C# compiler statically knows the type of x , it can do overload resolution and find the exact MethodA being called. 好的,现在因为C#编译器静态地知道x的类型,它可以进行重载解析并找到被调用的确切 MethodA。 Then it emits MSIL to push the arguments onto the MSIL virtual stack and issues a call instruction containing a metadata reference to that particular method. 然后它发出MSIL以将参数推送到MSIL虚拟堆栈并发出包含对该特定方法的元数据引用的调用指令。 No type checks are needed at runtime. 运行时不需要进行类型检查。

For x.ToString() , the C# compiler still knows the exact method it wants to call. 对于x.ToString() ,C#编译器仍然知道它想要调用的确切方法。 If ToString has been overridden by the struct type, it expects a parameter of type pointer-to- MyStruct , which the compiler handles without boxing. 如果ToString已被struct类型覆盖,则它需要一个类型为pointer-to- MyStruct的参数,编译器在没有装箱的情况下处理该参数。 If ToString has not been overridden, the compiler generates a call to Object.ToString , which expects an object as its parameter. 如果尚未覆盖ToString ,则编译器会生成对Object.ToString的调用,该调用期望将对象作为其参数。 To push x on the MSIL virtual stack as the correct type requires boxing. 要在MSIL虚拟堆栈上推送x ,因为正确的类型需要装箱。

GetType is a special case when the type is known statically, the compiler won't call any method, it just gets the type information from the symbol table and stuffs the metadata reference into the MSIL directly. GetType是一种特殊情况,当静态地知道类型时,编译器不会调用任何方法,它只是从符号表中获取类型信息并直接将元数据引用填充到MSIL中。

EDIT: Thanks for comments. 编辑:感谢您的评论。 I thought that I understand how it works... not anymore. 我以为我理解它是如何工作的......不再了。 So I'll leave this as starting point for investigation, but not an answer. 因此,我将此作为调查的起点,但不是答案。

There could be boxing for calling ToString or other virtual functions on structs because there is no need for v-table lookup. 因为不需要v-table查找,所以可能存在用于调用ToString或结构上的其他虚函数的装箱。 Structs are sealed, so exact method is known and compile time. 结构是密封的,所以确切的方法是已知的并且编译时间。

On other hand as pointed in comments virtual functions from base class need Object as "this" parameter. 另一方面,如注释中所指出的,基类中的虚函数需要Object作为“this”参数。

On third hand looking at generated IL it is unclear if ToString and GetHashCode actually do boxing (likly it is hidden somewhere since there is comment about boxing in these cases here http://blogs.msdn.com/b/lucabol/archive/2007/12/24/creating-an-immutable-value-object-in-c-part-iii-using-a-struct.aspx ). 在第三方面查看生成的IL,目前还不清楚ToString和GetHashCode是否实际上做拳击(很可能它隐藏在某处,因为在这些情况下有关于拳击的评论http://blogs.msdn.com/b/lucabol/archive/2007 /12/24/creating-an-immutable-value-object-in-c-part-iii-using-a-struct.aspx )。 GetType definietly requires explicit boxing. GetType绝对需要明确的拳击。

Looking at output of ILDasm to see if there is boxing or direct call: 查看ILDasm的输出以查看是否有拳击或直接呼叫:

       int v = 42;

        string s = v.ToString();

        object a = v;
        s = a.ToString();

Get compiled (debug) into following IL. 将编译(调试)编译到以下IL中。 There is no boxing for int.ToString(), but definitely one for casting to object... int.ToString()没有装箱,但绝对一个用于转换为对象...

  IL_0001:  ldc.i4.s   42
  IL_0003:  stloc.0
  IL_0004:  ldloca.s   v
  IL_0006:  call       instance string [mscorlib]System.Int32::ToString()
  IL_000b:  stloc.1
  IL_0013:  ldloc.0
  IL_0014:  box        [mscorlib]System.Int32
  IL_0019:  stloc.2
  IL_001a:  ldloc.2
  IL_001b:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0020:  stloc.1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM