Delen via


To box or not to box, that is the question

Suppose you have an immutable value type that is also disposable. Perhaps it represents some sort of handle.

struct MyHandle : IDisposable
{
public MyHandle(int handle) : this() { this.Handle = handle; }
public int Handle { get; private set; }
public void Dispose()
{
Somehow.Close(this.Handle);
}
}

You might think hey, you know, I'll decrease my probability of closing the same handle twice by making the struct mutate itself on disposal!

public void Dispose()
{
if (this.Handle != 0)
Somehow.Close(this.Handle);
this.Handle = 0;
}

This should already be raising red flags in your mind. We're mutating a value type, which we know is dangerous because value types are copied by value; you're mutating a variable, and different variables can hold copies of the same value. Mutating one variable does not mutate the others, any more than changing one variable that contains 12 changes every variable in the program that also contains 12. But let's go with it for now.

What does this do?

var m1 = new MyHandle(123);
try
{
// do something
}
finally
{
m1.Dispose();
}
// Sanity check
Debug.Assert(m1.Handle == 0);

Everything work out there?

Yep, we're good. m1 begins its life with Handle set to 123, and after the dispose it is set to zero.

How about this?

var m2 = new MyHandle(123);
try
{
// do something
}
finally
{
((IDisposable)m2).Dispose();
}
// Sanity check
Debug.Assert(m2.Handle == 0);

Does that do the same thing? Surely casting an object to an interface it implements does nothing untoward, right?

.

.

.

.

.

.

.

.

Wrong. This boxes m2. Boxing makes a copy, and it is the copy which is disposed, and therefore the copy which is mutated. m2.Handle stays set to 123.

So what does this do, and why?

var m3 = new MyHandle(123);
using(m3)
{
// Do something
}
// Sanity check
Debug.Assert(m3.Handle == 0);

.

.

.

.

.

.

.

.

.

.

 

Based on the previous example you probably think that this boxes m3, mutates the box, and therefore the assertion fires, right?

Right?

Is that what you thought?

You'd be perfectly justified in thinking that there is a boxing performed in the finally because that's what the spec says. The spec says that the "using" statement's expansion when the expression is a non-nullable value type is

finally
{
((IDisposable)resource).Dispose();
}

However, I'm here today to tell you that the disposed resource is in fact not boxed in our implementation of C#. The compiler has an optimization: if it detects that the Dispose method is exposed directly on the value type then it effectively generates a call to

finally
{
resource.Dispose();
}

without the cast, and therefore without boxing.

Now that you know that, would you like to change your answer? Does the assertion fire? Why or why not?

Give it some careful thought.

.

.

.

.

.

.

.

.

.

.

The assertion still fires, even though there is no boxing. The relevant line of the spec is not the one that says that there's a boxing cast; that's a red herring. The relevant bit of the spec is:

A using statement of the form "using (ResourceType resource = expression) statement" corresponds to one of three possible expansions. [...] A using statement of the form "using (expression) statement" has the same three possible expansions, but in this case ResourceType is implicitly the compile-time type of the expression, and the resource variable is inaccessible in, and invisible to, the embedded statement.

That is to say, our program fragment is equivalent to:

var m3 = new MyHandle(123);
using(MyHandle invisible = m3)
{
// Do something
}
// Sanity check
Debug.Assert(m3.Handle == 0);

which is equivalent to

var m3 = new MyHandle(123);
{
MyHandle invisible = m3;
try
{
// Do something
}
finally
{
invisible.Dispose(); // No boxing, due to optimization
}
}
// Sanity check
Debug.Assert(m3.Handle == 0);

It is the invisible copy which is disposed and mutated, not m3.

And that's why the compiler can get away with not boxing in the finally. The thing that it is not boxing is invisible and inaccessible and therefore there is no way to observe that the boxing was skipped.

Once again the moral of the story is: mutable value types are enough pure evil to turn you all into hermit crabs, and therefore should be avoided.

Comments

  • Anonymous
    March 14, 2011
    foreach and the special structs on the generic collections Enumerators would like a word :) At least the expansion of that sugar is reasonable (as in doesn't surprise most people)

  • Anonymous
    March 14, 2011
    While totally agreeing with everything you've said, mutable value types could be an argument for adding support for C++ style copy constructors and out-of-scope destructors for C# value types.

  • Anonymous
    March 14, 2011
    I think all readers of your blog now agree that mutable value types are evil... But I wonder why this rule is so often not applied in the .NET Framework classes (DictionaryEntry, GCHandle, Point, Size...)

  • Anonymous
    March 14, 2011
    @Thomas Levesque: I don't think that "mutable value types are evil" was known in the .NET 1.0 timeframe, at least not as well as we know it now, and all of your examples are from 1.0. A case could be made for e.g. List<T>.Enumerator, though you're not normally supposed to directly access that type, so it's (plausibly) less of an issue.

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    @Jonathan Pryor, you're right of course... but WPF (.NET 3.0) also has its own Point and Size structures, and they're also mutable. @Shuggy, yes, I think that's because it's mapped directly to the unmanaged structure...

  • Anonymous
    March 14, 2011
    The WPF structs (like Point and Size) have to be mutable because XAML cannot create immutable objects (it can only call default constructors and set properties). The real question is why they are structs, seeing as how they have to be mutable. I'd guess that it's an optimization. You could potentially have millions of Point objects, and performance could really suffer if the memory footprint doubled and each one had to be individually allocated, initialized, and GCed.

  • Anonymous
    March 14, 2011
    Re: "Suppose you have an immutable value type that is also disposable". Suppose you have an invisible purple dragon. Disposing the value changes its state (though not necessarily the bits of the struct).  If its state can change, then it isn't immutable.

  • Anonymous
    March 14, 2011
    @Gabe @Thomas: XAML can create immutable objects just fine (it can create strings and other primitives, after all), but Xaml2006 has no way of explicitly declaring instances of immutable types with non-default constructors.  This renders immutable types with more than one member somewhat useless in XAML.  Your only option is to declare a default TypeConverter such that you could put a string representation in XAML and have it converted to an instance of an immutable type.  Then you could do this: <SomeObject Location="(0, 2)" /> e.g., where 'Location' is an immutable 'Point' with (x, y) members..  Xaml2009 includes support for non-default constructors, but the syntax is relatively verbose, and Xaml2009 wasn't around in .NET 3.0, so we're stuck with some mutable value types.

  • Anonymous
    March 14, 2011
    "foreach" is different - per language spec, it does not expand to using but rather directly to try/finally, and the spec also explicitly says that, for enumerators which are value types, Dispose() is called directly without boxing. The difference is that foreach calls different methods several times on the enumerator, so you can write a perverted implementation that would be able to spot boxing on that final Dispose() call.

  • Anonymous
    March 14, 2011
    I can not agree wholesale with the statement that "mutable value types are enough pure evil to turn you all into hermit crabs." The real problem is when we try to treat values as objects. This is a flaw in the "everything is an object" concept. When a struct implements an interface (especially if the interface implies mutability) you are treating a value like an object. Your example is analogous to the much simpler scenario of assigning the value of 'int i' to 'int j', incrementing the value of j, and expecting i to change as well. (I understand that an integer is "immutable," but I take the position that however j comes to be incremented, it is an implementation detail, whether the processor chooses to twiddle some bits [mutate] or wholly overwrite a value. The difference is philosophical; Schrodinger's cat is doing our math.) As long as structs are used only as "complex values," and are designed in this spirit, things tend to go okay. (I've written up these thoughts in a more elaborate and drown out manner at snarfblam.com/words) If you're implementing an interface with a struct, that's a clear sign you're doing something wrong.

  • Anonymous
    March 14, 2011
    @snarfblam It most certainly isn't. Implementing an interface that implies mutability is, but many values types perfectly reasonably implement things like: IEquatable<T>, IFormattable, IComparable<T> to name but a few. Interfaces do not imply boxing (take a look at the constrained opcode if you want to know why) but this is an implementation detail anyway since immutability means it doesn't matter if you take a copy (in boxing) anyway.

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    I kind of agree that this is more of the issue of treating value types like object. But I my opinion is that it is a compromise we should just accept. The main argument to add value types to C# instead of using class for everything, if I'm not wrong, is for performance. Then we should accept the issues introduced by that choice. Mutating value type is definitely useful for performance and it is common for C/C++ programs. If we are not able to harness it, we should go and find an easier job. Nullable<T> is a similar thing. To make it work perfectly as an object type, it should have been a class. But for performance reason, it is designed as a struct. So then we have to accept issues like: T varOfT = new T() may cause varOfT to have value null if T is a nullable type.

  • Anonymous
    March 14, 2011
    @lidudu: Performance is only sometimes a benifit if structs are immutable - in fact, the reason String is a reference type and not a value type is performance - but you hardly tell the difference between immutable reference types and immutable value types. There are certain types that are easier to implement and use as value types, however, especially those types we tend to think of as "values" rather than "things" - mathematical stuff like Pair, Point, Matrix, Color especially. A nitpick: it doesn't really make sense to talk about value types for C or C++, since they have no concept of the thing. To C, all variables hold values, just some values are references. Re: Nullable<T>: I'm not sure what you mean by work perfectly as an object type: to my understanding, that was not it's purpose. "new Nullable<T>()" being equal, and convertable to null, is odd, but in keeping with the purpose of Nullable<T> - to enable storing null in a value type. Certainly being able to store null should not be the requirement of being a reference type, given the effort people put into ensuring their reference types are not null!

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    Oh and strings are reference types for many reasons but chief will be because they are variable size, so taking a copy very time you passed a multi megabyte string would be madness!

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    The comment has been removed

  • Anonymous
    March 14, 2011
    You're using structs, you shouldn't care if it copies since that's what they do (or if you are doing some serious low level optimisation knowing you can use ref and accept the ugly is fine) The whole reason why immutability is a very real concern that shouldn't be brushed aside as "well you can change the variable so it everything is mutable really" is that it makes precisely the scenarios we are discussing here not matter

  • Anonymous
    March 14, 2011
    okay, looks like it avoids boxing (disclaimer, I've not looked at the resulting assembly void Main() {    var nasty = new FrobValue(0);    Frob(nasty); } public static void Frob<T>(T t) where T : UserQuery.IFrobber {    t.Frob(); } public static void FrobRef<T>(ref T t) where T : UserQuery.IFrobber {    t.Frob(); } public interface IFrobber {    void Frob(); } public struct FrobValue : IFrobber {   public FrobValue(int _) {}   void IFrobber.Frob() { } } produces (in LinqPad) IL_0000:  ldloca.s    00 IL_0002:  ldc.i4.0     IL_0003:  call        UserQuery+FrobValue..ctor IL_0008:  ldloc.0     IL_0009:  call        UserQuery.Frob Frob: IL_0000:  ldarga.s    00 IL_0002:  constrained. 01 00 00 1B IL_0008:  callvirt    UserQuery+IFrobber.Frob IL_000D:  ret         FrobRef: IL_0000:  ldarg.0     IL_0001:  constrained. 01 00 00 1B IL_0007:  callvirt    UserQuery+IFrobber.Frob IL_000C:  ret         IFrobber.Frob: FrobValue.UserQuery.IFrobber.Frob: IL_0000:  ret

  • Anonymous
    March 14, 2011
    @Shuggy: Well the reason you would want to avoid boxing is so you can mutate through an interface - if copying was OK, ((IDisposable)t).Dispose() would be equivalent if potentially an iota more work for GC. And noone's "brushing aside" the problems with mutability, just that it is somehow OK for reference types, but horrible and should never be done for value types. If anything, I would say immutablity is more useful for reference types than value types given the increased chance for unexpected aliasing and far higher chance for cross-thread access, not to mention simplifying the ambiguity about modifying the reference versus the object when "identity" is not clearly defined for a type.

  • Anonymous
    March 14, 2011
    @Simon "just that it is somehow OK for reference types, but horrible and should never be done for value types." Okay you and I simply disagree at a very fundamental level. Anything which has value copy semantics is an immensely bad choice for mutability. The copy operations are  silent, and subtle (there are many occasions when copies are taken), any refactoring which pulled a value type between them could change the semantics, various aspects of construction would be hideous, readonly instance fields with them in get very messy.   Not to mention the concept of boxing changing the lifetime of the value, how on earth is that supposed to work... The List<T>.Enumerator gets away with it in normal scenarios because using foreach means you can't even see the enumerator to mutate it.

  • Anonymous
    March 15, 2011
    @Shuggy: But that thinking is exactly what @snarfblam was referring to as the real cause of the problems people have with value types - value types are value types, not objects, of course they behave differently. Treat them as values, and you're fine. I would argue IDisposable is (in general) not what you would want for a value type, not because it requires mutability, but because it implies ownership - which is the exact opposite of a plain value. Sure, you can, and interop style handles are a good candidate, just remember that there will be more than one value floating around. The copy operations are not that subtle, the majority of use cases are extremely obvious when they are copied - assignment operator? Copy. Passing an argument? Copy. I'm not sure what you're reffering to with construction - the rules about value type constructors require good design here - no ownership, no magic on copy, no type invariants. I do agree about readonly instance fields to a certain degree, but consider that anything you can do with a non-readonly immutable value type you can do with a readonly mutable value type and vice-versa. "readonly" is nonsensical for all value types, not just mutable value types. (And the readonly immutable combo is useful for both reference and value types). And both boxing and unboxing are each another copy, not that complex. To sum up: value types are not reference types and you should not think of them like that. They have different rules for design, different semantics when you use them, and different (not better or worse!) performance. If you treat them right, mutability is just as useful for them as for reference types. Whoops - I've gone into total wall of text mode - sorry about that :)

  • Anonymous
    March 15, 2011
    there's no point in using value types unless you want it to be copied and immutable, so your style is just bad. There's no performance improvement as well, specs don't say value types are allocated on stack, it just happened to be like that in current versions of .net.

  • Anonymous
    March 15, 2011
    public static void Frob<T>(T t) where T : UserQuery.IFrobber {   t.Frob(); } This should not involve boxing by definition. At run time it is calling t of a concrete type rather than an interface, because generic functions will be instanced for each value type during JIT.

  • Anonymous
    March 15, 2011
    @Simon: "Re: Nullable<T>: I'm not sure what you mean by work perfectly as an object type: to my understanding, that was not it's purpose. "new Nullable<T>()" being equal, and convertable to null, is odd, but in keeping with the purpose of Nullable<T> - to enable storing null in a value type. Certainly being able to store null should not be the requirement of being a reference type, given the effort people put into ensuring their reference types are not null!" I mean, if Nullable<T> was a class, it would follow reference type behavior (same as object) naturally. Rather than having various special behaviors different than either value type or reference type even though it does be a value type:            int? i = new int?(); // i is null            Console.WriteLine(i.ToString()); // work            Console.WriteLine(i.GetType()); // NullReferenceException            i = 10;            object obj = i; // boxed int rather than boxed Nullable<int>            var ic = (IComparable<int>)i; // i is boxed and then queried for interface rather than query directly on struct But anyway, I agree much of your points, especially that value type should not implement IDisposable. My core point is just that mutable value types are not always evil.

  • Anonymous
    March 15, 2011
    The comment has been removed

  • Anonymous
    March 15, 2011
    The comment has been removed

  • Anonymous
    March 15, 2011
    For those wondering why mutable value types (with public fields, no less!) are even allowed if they're so evil, see Rico Mariani's posts about when you actually need them: blogs.msdn.com/.../733887.aspx and blogs.msdn.com/.../745085.aspx

  • Anonymous
    March 15, 2011
    The comment has been removed

  • Anonymous
    March 15, 2011
    @Simon, @lidudu: Here's an example of what I was talking about:        public struct MyHandle : IDisposable        {            public int Handle { get; private set; }            public MyHandle(int handle) : this() { this.Handle = handle; }            public void Dispose()            {                Console.WriteLine("Disposing Implicit");                this.Handle = 0;            }            void IDisposable.Dispose()            {                Console.WriteLine("Disposing Explicit");                this.Handle = 100;            }        }        public static void Test()        {            var m1 = new MyHandle(1);            m1.Dispose(); // <-- "Disposing Implicit"            var m2 = new MyHandle(2);            ((IDisposable)m2).Dispose(); // <-- "Disposing Explicit"            using (var m3 = new MyHandle(3))            {                //according to the article, should be Disposing Implicit                //actual value is Disposing Explicit            }        } So, we can implement both an implicit and an explicit version of the interface in the same class, and they will both be called in the appropriate context. However, the article stated that for this case, the compiler avoids boxing and calls directly, thus calling the implicit implementation, but in the example the explicit implementation is called.

  • Anonymous
    March 15, 2011
    @SWeko: That's one way to say it, but the runtime and CLR don't see it that way - you merely have a type that explicitly implements Dispose(), and also has an unrelated public method called Dispose(). It's sort of like "public new void Method()" without the inheritance - reusing the name without any polymophism or virtual slots being involved.

  • Anonymous
    March 15, 2011
    The comment has been removed

  • Anonymous
    March 16, 2011
    The comment has been removed

  • Anonymous
    March 16, 2011
    @SWeko: Yeah, the complaint was merely about calling it implicit implementation when it's not actually implementing. :) Thanks for the IL - now I need to go lookup what "constrained." does again. @Pavel: I was kinda hoping that "(StrongBox<int>)(object)obj;" would work. I'm trying to think what other compiler support would be usefull, none of the operators seem to make much sense (can't pick between assigning to variable or value, mostly). Thanks for the pointer, though!

  • Anonymous
    March 16, 2011
    @Pavel Minaev, from the page you linked to, "This API supports the .NET Framework infrastructure and is not intended to be used directly from your code."

  • Anonymous
    March 16, 2011
    @snarfblam: Everything in the "System.Runtime.CompilerServices" namespace gets that - it's a little overblown, I think it's just trying to say "This stuff is designed to be easy for compilers, not humans. Please don't complain about usability."

  • Anonymous
    March 16, 2011
    The comment has been removed

  • Anonymous
    March 16, 2011
    I feel that the issue is more of improper use of value type for types which need to manage ownership, etc. I don't think that's what value type is designed for, as per what .NET library design guidelines say. In those cases, reference type should be used instead of value type. If, for any reason, the coder really want it to be a value type, then design the type as immutable. @SWeko: it is interesting that it generates constrained. TestAppConsole.Structs/MyHandle But I kind of think it is consistent with generic case like: void Func<T>(T s) where T: struct, IDisposable {  s.Dispose(); } For T as your MyHandle struct, this function also calls IDisposable.Dispose() without boxing. Because Dispose() is called on the struct variable directly, rather than converting to IDisposable interface type first. If you do ((IDisposable)s).Dispose(), then you are explicitly creating a temporary variable of type IDisposable, which is a reference type, then it will surely produce IL code for boxing.

  • Anonymous
    March 16, 2011
    @lidudu: Good catch on the cast (though I preffer .Value) - but you are describing value semantics. Unless you want "a = 3" to mean value semantics and "a.Value = 3" to be either an null error or reference semantics, which is sane (and also System.Runtime.CompilerServices.StrongBox<T>). But I beleive the value-type behavoiur of Nullable<T> is entirely by design.

  • Anonymous
    March 16, 2011
    @Simon: Nullable<T>.Value is a read-only property and the type is immutable. Similar to string a = 'a' does not mean to alter a character of the object but to assign the variable a with a new object reference. If it was reference type and it translated to a.Value = 3, then int? a = null; a = 3; will throw NullReferenceException.

  • Anonymous
    March 17, 2011
    The comment has been removed

  • Anonymous
    March 18, 2011
    >> But I think, optimizing the cast away, offends the requirement of a consistent behavior, since casting introduces a boxing, which leads to a different behavior. It's not different if you can't observe it (the "as if" rule). Speaking of which, can anyone come up with some way of observing boxing (or lack thereof) during the call to Dispose in "using" (while remaining within the realm of language spec, obviously).

  • Anonymous
    March 18, 2011
    The comment has been removed

  • Anonymous
    March 18, 2011
    @Gabe: that is precisely while I said "remaining within the realm of language spec". The language spec does not define the effect a program will have on a profiler - thus, whether you see boxing there or not, it does not affect the conformity of a particular C# implementation. To prove that this is non-conforming behavior, you'd have to devise some scheme to observe the boxing behavior from within the program itself.

  • Anonymous
    March 18, 2011
    @Pavel: An edge case, and I'm sure the spec does not ensure fixed (this) is stable for local variables (that are not in an iterator or async method and are not captured by a lambda and therefore on the stack), so it may not be "within the language spec" to the letter, but I think this pattern is somewhat useful, for eeking out some more perf: unsafe struct PtrLock : IDisposable {    static HashSet<PtrLock*> locked = new HashSet<PtrLock*>();    public static DoSomethingToLocked() { ... }    bool isLocked;    public void Lock() { if (!isLocked) { isLocked = true; fixed (PtrLock* ptr = this) locked.Add(ptr); } }    void IDisposable.Dispose() { if (isLocked) { isLocked = false; fixed (PtrLock* ptr = this) locked.Remove(ptr); } } }

  • Anonymous
    March 20, 2011
    The comment has been removed

  • Anonymous
    March 21, 2011
    @Bill P. Godfrey "mutable value types could be an argument for adding support for C++ style copy constructors and out-of-scope destructors for C# value types." AAAIEEEE! Run for the hills! :) It's the other way round, surely. The problems caused by mutable value types form a very strong argument for not creating mutable value types, and would not be solved by those features anyway. The question is, why have structs at all? Obviously we're stuck with them now, but if they didn't exist would they need to be invented? The answer is no. It's common for C++ users to assume that GC-ed (heap allocated) reference types will perform much more poorly than stack-allocated value types, but this is based on their experience with C++ native heaps, which are non-compacting and so perform very poorly. This may be the reason why custom value types were added to the CLR in the first place. But the reality in the present-day CLR is quite different - the CLR GC heap is so blazingly fast, performance is hardly ever a reason to choose between 'struct' and 'class'. See: msdn.microsoft.com/.../dd942829.aspx So you don't get a performance boost from struct over class, and yet you have to give up so much in terms of language features. It's a very poor trade. And are copy constructors really something to pine over? If something is immutable, there is never a reason to make an exact copy of it. The original will do just fine - it's never going to change. In CLR/Java, if you find yourself thinking "Dang, I wish I could write a copy constructor for this class", try making it immutable instead, and watch your troubles evaporate! You will be able to treat references to it as if they were values (as long as you override ==/!= appropriately). Strings are the really telling example here. By making them immutable GC-ed objects, runtimes like the CLR and the JVM actually provide measurably better performance than Standard C++ programs using appropriately-designed classes with copy constructors, i.e. the std:: stuff. See the famous showdown: blogs.msdn.com/.../416151.aspx

  • Anonymous
    March 21, 2011
    Daniel: I find it ironic that you used a Rico Mariani blog post to support your point that structs are unnecessary, while I used one (blogs.msdn.com/.../733887.aspx) to support my point that they are necessary. In fact, if they didn't exist, somebody would have probably had to invent them. That's why NumPy exists -- Python doesn't have low-overhead types like struct so somebody had to create them in a C module. If you look at Rico's MeshSection example, you'll see that Point3d is 24 bytes, Point2d is 16 bytes, Vector3d is 24 bytes, Vertex is 64 bytes, and Quad is 16 bytes. Allocating a MeshSection allocates exactly 3 objects (the MeshSection, the Vertex array, the Quad array, and ignoring the TextureMap). Creating a MeshSection with 1k vertices and quads will use 64k bytes for the Vertex[], 16k bytes for the Quad[], and maybe 64 bytes of overhead, for a total of 80k. Accessing any element of either array (even vertices[123].normal.dx) requires finding the start of the array, multiplying the size of each element by the index, and adding in the offset to the field. GC overhead is negligible because there are only 3 objects. If this had to be done with only reference types (on a 32-bit machine where each object has 8 bytes overhead), Point3d is 32 bytes, Point2d is 24 bytes, Vector3d is 32 bytes, Vertex is 20 bytes, and Quad is 24 bytes. Allocating a MeshSection with N vertices and quads allocates 3 + 5N allocations (5k in this case). The memory used is 64 +4N (array of references to Vertex) + 4N (array of references to Quad) + 20N (instances of Vertex) + 32N (instances of Point3d) + 24N (instances of Point2d) + 32N (instances of Vector3d) + 24N (instances of Quad), for a total of 140k. Now to access vertices[123].normal.dx you have to find the offset into the Vertex[], dereference the Vertex, dereference the Vector3d, and find the offset to the field. So for this example, using only reference types nearly doubles the amount of memory used, makes accessing fields several times more work, and turns the memory management (allocation and GC) overhead from being constant to being linear. I would argue that certainly the combination of all of these is reason enough to implement value types in .Net.

  • Anonymous
    March 21, 2011
    The comment has been removed

  • Anonymous
    March 21, 2011
    I would write a reply to @Daniel, but @Gabe and @Shuggy covered everything. Well, I should add that the object header also add some number of bytes close to the size of those structs (8 or 16 bytes, on 32bit I think?) which makes the memory usage much worse, not to mention if the allocation of all those objects is not all together (unlikely in the context of an model editor, say) that any processing will be all over the place in memory access, which will be the reall killer as your 30000 vertices need 3000 page acesses rather than 60 (and each extra page pushes another page out, remember!).

  • Anonymous
    March 21, 2011
    The comment has been removed

  • Anonymous
    March 22, 2011
    The comment has been removed

  • Anonymous
    March 22, 2011
    The comment has been removed