UGTS Document #8 - Last Modified: 8/29/2015 3:23 PM
.NET Delegates and Type Safety
.NET is a 'managed' programming environment where certain classes of errors are nearly impossible to generate because the runtime and compiler restrict what kind of things that the code can do. For example, buffer overruns are don't usually happen because bounds-checking is enforced on all array access, and if you try to write or read out of bounds of the array, an exception is thrown.
.NET also denies you direct access to read and write to memory locations, unless you use 'unsafe' code blocks and compile your project with the /unsafe option. If you use the 'unsafe' option, you are bypassing the safety constraints in .NET, and your code may crash in unrecoverable, unreportable, and unfriendly ways (that is, possibly taking other .NET assemblies down with that have no problems simply because they ran in the same process and the crashing application corrupted the memory of that process).
Being able to directly read and write arbitrary memory addresses means that you can do anything. You can't directly jump to any location in memory lke you might do in assembler, but you can get the raw memory address of a delegate object, use it to overwrite the memory of that delegate, and then call that delegate. Your process is then in la-la land, probably for about a microsecond before the process crashes and Windows shuts it down. If you try to do this to execute data and Data Execution Prevention is turned on, you won't be able to do it at all, because the CPU will crash the process immediately. Theoretically you could use this technique to have the program dynamically modify its own code. I never have tried this or needed to try it. There have always been safe ways to get the job done, and why give up safety if you don't have to?
Apart from using unsafe code or bizarre hacks with calls to unmanaged DLL and Marshal calls, you can't execute code at arbitrary memory locations. The compiler requires that every method pointer variable (delegate) be type safe - that it exactly matches the calling convention of the method that it points to (apart from Covariance and Contravariance). You can't cast a delegate to anything but object, or to a delegate of the same exact type, or from an object box back to the original delegate type. Even if two delegate types have the exact same signature, you can't cast between them. Here are some examples illustrating this:
public delegate bool CharMatcher(char c);
public delegate bool CharMatcher2(char c);
public delegate bool CharMatcher3(string s);
// Example #1: only the first conversion will work, b2 and b3 will get a null value:
CharMatcher a = char.IsWhiteSpace;
object o2 = a;
var b = o2 as CharMatcher;
var b2 = o2 as CharMatcher2;
var b3 = o2 as CharMatcher3;
// Example #2: the compiler will allow this, but you'll get a run-time error when you try to cast
// the boxed CharMatcher object to type Integer
CharMatcher c = char.IsWhiteSpace;
object o = c;
var i = Convert.ToInt32(o);