(1) [Avatar] Offline
In generics chapter the writer has written
"the JIT can treat value types in a particularly
clever way that manages to eliminate boxing and unboxing in many situations. In
some cases, this can make a huge difference to performance in terms of both speed
and memory consumption.'

Please explain with an explanation what is the clever way.

Message was edited by:
mgravell (64) [Avatar] Offline
Re: Jit Value types treating
(important: I'm not the author)

Without trying to second-guess Jon, I expect that is referring to "constrained" calls. Normally, people know about 2 types of call:

- callvirt, which operates on an object reference to invoke an instance method
- call, which is a *static* call

Normally, most (not quite all) things involving structs are done with "call"; however, if (in non-generic code) you want to call a method on an interface, you need to cast to that interface:

SomeStruct foo = ...
ISomeInterface bar = foo; // "a" below
bar.SomeMethodOnTheInterface(); // "b" below

this, however, involves a box at "a", and a callvirt at "b".

Generics are clever, though. There is a special opcode (added in CLR 2) that is a "best of both" - i.e. "constrained"; it is interpreted as:

- if the value at this address is a value type and implements this method directly (i.e. not just inherited from "object"), then call the specified method via static "call"
- the the value at the address is a value type and does not implement this method directly, box it to the declaring type of the specified method, and call the specified method via "callvirt"
- otherwise, for reference types, dereference the object and call the specified method via "callvirt"

This means that you can write:

void Clever<T>(T someVal) where T : ISomeInterface {

and even if the "T" specified at runtime is a value-type, it will not need to box "someVal" to call the method.

In case you were wondering what the "implements the method directly" stuff is about, it can also be used on the methods from "object", i.e. ToString(), Equals() and GetHashCode(); the important point is that if you "override" these methods in a value-type, then "constrained" can call it via static "call", even in non-generic code. This is just one of many reasons why you should make a habit of overriding those methods, on the rare occasions when you need a custom struct.

It makes the decision about "call" vs "callvirt" (for "constrained" calls) during JIT, based on what is known about the types; it doesn't do this per-instance, i.e. this will never use "constrained"

object o = ...
string s = o.ToString();

From memory, "constrained" is primarily used when either:

- a call is made against a value of type T (for some generic type parameter T) in a generic method, where T does not include a ": class" constraint
- a call to one of .Equals / .GetHashCode / .ToString is made against value-type SomeStruct

Final note: GetType() is non-virtual, so will always involve a box / "callvirt" - as it cannot be overridden in the struct.