Possible to Inspect Innards of Core C# Functionality

645 views Asked by At

I was struck today, with the inclination to compare the innards of Buffer.BlockCopy and Array.CopyTo. I am curious to see if Array.CopyTo called Buffer.BlockCopy behind the scenes. There is no practical purpose behind this, I just want to further my understanding of the C# language and how it is implemented. Don't jump the gun and accuse me of micro-optimization, but you can accuse me of being curious!

When I ran ILdasm on mscorlib.dll I received this for Array.CopyTo

.method public hidebysig newslot virtual final 
    instance void  CopyTo(class System.Array 'array',
                          int32 index) cil managed
{
  // Code size       0 (0x0)
} // end of method Array::CopyTo

and this for Buffer.BlockCopy

.method public hidebysig static void  BlockCopy(class System.Array src,
                                            int32 srcOffset,
                                            class System.Array dst,
                                            int32 dstOffset,
                                            int32 count) cil managed internalcall
{
  .custom instance void System.Security.SecuritySafeCriticalAttribute::.ctor() = ( 01 00 00 00 ) 
} // end of method Buffer::BlockCopy

Which, frankly, baffles me. I've never run ILdasm on a dll/exe I didn't create. Does this mean that I won't be able to see how these functions are implemented? Searching around only revealed a stackoverflow question, which Marc Gravell said

[Buffer.BlockCopy] is basically a wrapper over a raw mem-copy

While insightful, it doesn't answer my question if Array.CopyTo calls Buffer.BlockCopy. I'm specifically interested in if I'm able to see how these two functions are implemented, and if I had future questions about the internals of C#, if it is possible for me to investigate it. Or am I out of luck?

5

There are 5 answers

0
Nick Babcock On BEST ANSWER

I am going to answer my own question. I realize that this is bad style, but I could not have formulated the answer if it were not for the wonderful resources that previous answers have supplied me. Thank you.

First off, those coming here and wondering how to, in general, inspect the innards of C# functions, Tim posted a wonderful resource, ILSpy. This works in the cases where the methods are not defined externally. When they are defined externally, it seems like the only hope for getting an answer is if you download the SSCLI 2.0. Since this is targeted for .Net 2.0 and not 4.0, the information I present may be dated. However, I will continue with the assumption that the methods in question have not changed much. After looking through the source files, I believe I can answer the question “does Array.CopyTo call Buffer.BlockCopy behind the scenes?”

Before I get to the heart of the matter, others have pointed out that CopyTo calls Array.Copy. Array.Copy is defined externally, so I will change my query to “does Array.Copy call Buffer.BlockCopy behind the scenes?” A little tidbit that I found interesting is from the documentation of Array.CopyTo on MSDN

If implementing System.Collections.ICollection is not explicitly required, use [Array.]Copy to avoid an extra indirection.

Let me distinguish the types of checks that must be true for each function to perform:

BlockCopy:

  1. Destination and Source cannot be null
  2. Destination and Source array are composed of primitives or strings but no objects.
  3. The lengths and offsets must be valid

Array.Copy:

  1. Destination and Source cannot be null
  2. Destination and Source must be an array
  3. Several checks with method tables and ranks
  4. Slightly more in-depth length and offset checks
  5. Types must match somehow (either with un/boxing, casting, or widening)

After these checks, BlockCopy calls m_memmove, which is self-explanatory, yet highly interesting. m_memmove is architecture dependent; forgoing traditional memmove on 32-bit machines in favor for a hand rolled 16 bytes at a time copy.

Arrays, as can be imagined, hold more than just primitives.

  • If the source and destination array are of the same type, Copy calls m_memmove. Then, if the underlying type is not a primitive, a garbage collection action is enacted.
  • Else, depending on the type of elements passed in, Copy will unbox, box, cast, or widen each element as it transfers them. These functions, as far as I can tell, do not call m_memmove, and instead convert each element one at a time.

Therefore, to answer my original question, “does Array.Copy call Buffer.BlockCopy behind the scenes?” Sort of, if we consider the similarities the two methods have in common dealing with m_memmove. BlockCopy will always call m_memmove whereas Copy will only call it if it is dealing with arrays of exactly the same type. It is then my recommendation, if one wanted to copy an array of bytes to ints, or something similar where they just cared about the raw data, to use BlockCopy as it will be able to take advantage of m_memmove.

For reference: what .Net defines as a primitive (taken from cortypeinfo.h)

  1. Void
  2. Bool
  3. Char
  4. Signed and Unsigned Byte
  5. Signed and Unsigned short
  6. Signed and Unsigned int
  7. Signed and Unsigned long
  8. Float and double
  9. Signed and Unsigned IntPtr
  10. ELEMENT_TYPE_R (not sure what this is)
2
Tim S. On

ILSpy is a free .NET decompiler. You can use it to examine any .NET DLL, including mscorlib.

Array.CopyTo calls Array.Copy. Both Array.Copy and Buffer.BlockCopy are extern methods, meaning they're defined in native code instead of managed .NET code, so I can't tell you any more about how they work.

0
Guffa On

When I use Telerik JustDecompile on Buffer.BlockCopy I find:

[SecuritySafeCritical]
public static extern void BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count);

Which means that it's not implemented using something that produces IL code.

And Array.CopyTo:

public void CopyTo(Array array, int index) {
  if (array != null && array.Rank != 1) {
    throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported"));
  }
  Array.Copy(this, this.GetLowerBound(0), array, index, this.Length);
}

Array.Copy reveals:

[SecuritySafeCritical]
[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
public static void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length) {
  Array.Copy(sourceArray, sourceIndex, destinationArray, destinationIndex, length, false);
}

That overload:

[SecurityCritical]
[ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
internal static extern void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable);

So that's also not IL code.

If you want to examine the methods, you have to bring out the disassembler.

5
Hans Passant On

The biggest issue here is that you are looking at the reference assemblies, particularly for those of .NET 4, judging from what you posted. Which are special assemblies, they have all their IL stripped out and just contain the metadata. This is new in .NET 4, it solves an old problem in earlier versions of .NET. Where the reference assemblies were simply a copy of the actual assembly installed in the GAC.

This caused trouble, changes were made in later releases and service packs that were breaking programs. Especially the WaitHandle.WaitOne(int) overload was notorious, it was added in .NET 3.0 (aka .NET 2.0 SP1). And unwittingly used by programmers that found that overload a heckofalot easier to use then the mysterious WaitOne(int, bool) overload. But with the problem that their program would no longer run on the original .NET 2.0 release version, producing a MissingMethodException.

Adding this overload was in general a pretty naughty thing to do, they modified mscorlib.dll but did not change its [AssemblyVersion]. By providing separate reference assemblies in .NET 4, this problem cannot occur anymore. Microsoft can now modify the public interface of .NET types without breaking anything. And have done so with gusto, several .NET 4 intermediate releases have been slip-streamed without anybody noticing.

So be sure to disassemble the real version of mscorlib.dll, the one in the GAC. Which for .NET 4 is stored in a different directory, c:\windows\microsoft.net\assembly, not c:\windows\assembly. Which no longer is protected by the explorer shell namespace extension, you can simply use File + Open to browse the GAC directories. You'll find the 32-bit version in the C:\Windows\Microsoft.NET\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089 directory.

That isn't quite the end of the story, when you drill down you'll find Array.CopyTo() calling an internal helper method named Array.Copy() that has the [MethodImpl(MethodImplOptions.InternalCall)] attribute. Again, without a method body. The attribute tells the just-in-time compiler that the method is actually implemented in C++ inside the CLR. See this answer to find out how to see the source code for such methods.

0
spender On

Both Array.CopyTo and Buffer.BlockCopy are (ultimately) implemented with the pseudo-attribute:

[MethodImpl(MethodImplOptions.InternalCall)]

If you take a look at the second question on this page from general clever-man, Steven Toub, there's and excellent explanation of what this means.