When is code used for different instances of generics in the CLR?

If you have a method or type Foo<T> , then the CLR can compile several versions for different T. I know that all reference types use the same version. How does it work for structures? Is the code sometimes shared or never shared across different structures? I could imagine that the code is used for all structures of the same size, for example.

I'm curious because I'm curious about the following example:

 interface IBar { void DoBar(); } struct Baz : IBar { public void DoBar(){ ... } } struct Quux : IBar { public void DoBar(){ ... } } 

Now, if I do the following:

 public void ExecuteBar<T>(T bar) where T:IBar { bar.DoBar(); } ExecuteBar(new Baz()); ExecuteBar(new Quux()); 

Will it generate two versions of ExecuteBar , each of which has a direct (non-virtual) call directly to Bar.DoBar() and Quux.DoBar() ? Or is sending being done at runtime?

+5
source share
3 answers

There is no direct answer to this question, it greatly depends on the jitter used and what code is present in the general method.

The starting point is that jitter generates a separate method for each individual value type. Even for structures that are otherwise completely identical. Something you can see with a debugger. Use Debug + Windows + Disassembly to view the generated machine code. One step into the method, use the Debug + Windows + Registers window, the EIP / RIP register shows the location of the method in memory.

But general methods like this are still eligible for inlining optimization. This is very important for the performance, and the whole method disappears, and the method code is entered into the caller's method. In this case, the distinction between generic methods disappears. This is not something you can usually rely on for interface implementation methods. However, if you leave the method body empty, this will happen for your sample code. With different results for x86 and x64 jitter.

You can only say what you get by looking at the generated machine code. Make sure that you allow the optimizer to do its work, "Tools + Options", "Debug", "General", disable the "Suppress JIT optimization" checkbox. And of course, make sure you never depend on the exact answer to this question. Implementation details like this are subject to change without notice.

+4
source

General definitions are not used directly. Instead, constructed types are created at runtime.

For each value type argument, one construct will be created. A unique construct will be created for all arguments of reference types.

Afaik, different types do not have the same asm instructions.

+1
source

I put your code in LINQPad, and the generated IL shows that there is only one version of ExcuteBar that calls IBar.DoBar ()

 ExecuteBar: IL_0000: nop IL_0001: ldarga.s 01 IL_0003: constrained. 01 00 00 1B IL_0009: callvirt UserQuery+IBar.DoBar IL_000E: nop IL_000F: ret 

The ExecuteBar2 method (IBar panel) looks similar:

 ExecuteBar2: IL_0000: nop IL_0001: ldarg.1 IL_0002: callvirt UserQuery+IBar.DoBar IL_0007: nop IL_0008: ret 

Edit:

 class C : IBar { public void DoBar(){} } 

The type of the link is passed as an argument to the same method (Baz and Quux in the box)

0
source

Source: https://habr.com/ru/post/1201587/


All Articles