Вопрос

I have been reading Eric Lippert’s blog, particularly on topics about heap, stack and registers and from what I can understand the decision to place a variable on heap or stack is mostly to do with the ‘life time’ of the variable i.e. ‘short lived’ or ‘long lived’ and if anything done to a variable on stack that increases it's life time beyond the life time of the function its declared in, it becomes a candidate for a heap ‘promotion’ via a compile time wrapper class, like in the case of stack variables used in closures. So the question is why the .net compiler (still) does not identifies candidates which need boxing and choose to implement a class, which of course will always be allocated on the heap? And in turn do away with boxing altogether?

Это было полезно?

Решение

I'm going to answer the question you asked in your comment, which I think is the same question just stated another way, because I think it will help eliminate your confusion.

I am just curious as to why there exists two different ways to deal with two seemingly similar scenarios i.e. boxing vs value types in closures.

There are two operations you're talking about here, "boxing" and "lifting", and they do two completely separate things. It is an implementation detail that they happen to do those things via similar means, but they solve separate problems and have separate requirements.

The purpose of boxing is to allow value types to be stored as reference types and extracted later. It has nothing to do with the scope of the variables in question, and everything to do with maintaining type safety. Boxing can occur entirely within the local scope of a variable, e.g.:

int i = 1;
object o = i;
int j = (int)o;

But, more often it's used when there's an need to pass a value type to a parameter that expects a reference type, e.g.:

string.Format("The value is {0}", 10);

string.Format takes a params object[] parameter, so every value type passed into the method is boxed. In the CLR's type system, all value types inherit from System.Object, so treating a value type as an object is always a safe operation. The unboxing operation, on the other hand, relies on the developer to unbox the right things from the right boxes, a verification that can only happen at run-time, as the compiler has no way of knowing for sure what the "real" values stored in those objects are without at compile time.

The other operation, lifting, is used to alter the default lifetime of an identifier that would normally follow from it's lexical scope. This lifting operation has to be done for any data types, values or reference types, that are about to leave scope but must be maintained (e.g. they were closed over by a lambda). This is done not to change the representation of the data type, but to make sure the values are available after the method returns and prevent it from garbage-collecting the now-unreachable reference instances.

Note that "lifted" value types are not boxed. The compiler creates a class to represent the entire closure, which includes value-type members for any closed-over value-type identifiers. Those value types are never pushed into an object and pulled out later, any more than your own value-type fields would be.

You seem to be focusing on the fact that both of these operations happen to be implemented by way of the creation of a new instance of a class that "contains" the boxed or lifted type. But that really shouldn't surprise you. Everything in .NET is done through objects. That one common thread does not make those operations similar enough to eliminate either one of them. If you tried to merge them into a single operation, you would likely end up with a highly inefficient operation that simply did both things all the time, when that's rarely what is needed.

Другие советы

In the example you give - closures are handled at compile time and boxing happens at run time but in both examples you are promoting the lifetime of the variable.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top