Dead C Hacks: Reallocating or Changing an object's type in place in .NET? [Brian Grunkemeyer]

I was recently asked if there was a way to reallocate an object on top of another one, or to change the type of an object at runtime. This must have been a somewhat common practice in native C or C++ programs, perhaps something that C++'s placement new facilitated. I'm not a big fan of the idea in general, and it can't be done in the CLR. Here was my response, with additional ramblings:

The only way to change the type of an object is to find the call to "new A()" and replace it with "new B()". This can be made easier by providing factory methods in some cases, so all your client code goes through a wrapper you provide and you allocate the appropriate data yourself on their behalf. But fundamentally we don't have anything like C++'s placement new to allocate B's on top of A's. That would be difficult to implement considering that B's are larger than A's and we've used the space after A for other data structures.

A similar approach we've taken to replacing an allocated object with another one is Array's Resize<T> method, which we could only properly design once we added generics into the language in Whidbey (generics provided very necessary syntactic sugar, helping you avoid a cast). See the code below. Look at how we use the ref parameter here - you might find a technique like this useful. However, ref parameters only affect the individual reference to an object at that call site - if you have multiple references pointing to the same object, they will still point to the old object, not the new one.

 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] 
public static void Resize(ref T[] array, int newSize) { 
    if (newSize < 0) 
        throw new ArgumentOutOfRangeException("newSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); 
 
    if (array == null) { 
        array = new T[newSize]; 
        return; 
    } 
    if (array.Length != newSize) { 
        T[] newArray = new T[newSize]; 
        Array.Copy(array, 0, newArray, 0, array.Length > newSize? newSize : array.Length); array = newArray; 
    } 
}

We had to design Array.Resize<T> like this because there's no way to reallocate an object _in place_ in the GC heap. Even if there were with some constraints like it must be the same type but of a different length, it could lead to security vulnerabilities. Imagine a race where someone read the length of an array as 1 million elements then started indexing in that array, and on a second thread someone reallocates that array to 0 elements long then starts allocating other objects in that newly-freed memory. Or if you change the type from one object to another of the same size, you could theoretically run into a race where another thread is calling a method, and suddenly the memory used by its "this" pointer changed to another type, (possibly) meaning virtual method calls called into another object! So even if we did expose this functionality, it would be very dangerous to use. I suspect we'll never build anything along these lines as a general purpose tool. (I've experimented with a technique like this once as a possible perf optimization for StringBuilder to shrink a String instance, but our GC architect was very, very upset. And the code probably didn't work correctly on IA64 due to the processor's weak memory model.)

Along similar lines, we also have added Interlocked.CompareExchange<T> (finally!). One interesting property is we can't make this work for arbitrarily sized value types, so we constrained T to be a reference type (note the "where T : class" syntax below).

 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 
public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class; 

Another interesting technique is allocating memory on the stack or in a block of unmovable heap memory (either memory mapped from a file, a pinned byte[], or something similar), taking the address of that memory, then casting that to a pointer to a value type. We use this a lot for loading our NLS+ data tables (character sorting information, etc). While this doesn't allow you to reallocate something in place, it will save you some memory copies from time to time, and is a generally useful technique if you like C#'s "unsafe" code. (The word unsafe bothers me, because our more complicated P/Invoke code is also unsafe, even though it doesn't explicitly have to use the unsafe keyword.  But it is still a useful flag to force someone to code review your work later in more detail.) 

The complication with this pointer into some fixed block of memory is controlling the lifetime of the memory - you must make sure your memory stays around until your objects using that memory are freed. If you don't do this, you're back to all the dangling pointer problems you may run into in native code, and these problems will be slightly more insidious to debug if you have interesting finalizers, since the finalizer thread will be actively injecting race conditions in your poorly-written code. And the biggest limitation is arrays - unlike C, our arrays are objects and must be allocated in the GC heap. To put data into a managed byte[], you must do a copy. I've been wanting us to explore both a byte[] and a String representation where the data can be in another heap since 1999, but frankly I don't think we can do the String optimization now. (Perhaps one day we'll spend some effort on the byte[] problem, but probably not for quite a while.) Perversely, C#'s fixed statement on Strings locked us into an implementation where the character data is a (somewhat) constant offset from the pointer to the String, meaning that enabling fast pointer manipulation of Strings for external users prohibits us from doing the most elaborate pointer-related tricks with Strings internally.

I hope this helps clarify some of how the CLR is designed, and the limitations it can place on issues. Note that most of these restrictions still existed in C and C++, but you could simply break the rules and your app would mostly work most of the time on some platforms. Hopefully our added discipline (or handicaps) will make debugging a vastly simpler task.