Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
The type "System.Array" provides us a set of API for the late-bound array operations, including array creation (Array.CreateInstance), read/write access to array element (Array.SetValue/Array.GetValue). They are convenient to use. Let me start with some code to create an one-dimensional integer array, and set the first 2 elements using early-bind assignment and Array.SetValue, respectively. To simplify my code, this post will only touch the late-bound set value on array (the similar discussion can be applied to get value).
// creation: new int[5]
Array x = Array.CreateInstance(typeof(int), 5);
// 0. early-bind set: explicit cast to int[]
int[] y = (int[])x;
y[0] = 100;
// 1. late-bound set via Array.SetValue
x.SetValue(200, 1);
From the signature of Array.SetValue(object, int), we know that the integer "200" has to be boxed first before passing into this API, which is a performance hit (among others). However, we can take a look at what kind of IL sequence C# compiler generates for the early-bound assignment, and build an equivalent dynamic method to avoid the boxing: C# compiler emits Stelem.I4 to store the 32-bit integer value.
// 2. late-bound set via DynamicMethod/Delegate/OpCodes.Stelem
DynamicMethod dm = new DynamicMethod("SetValueByStelem", typeof(void),
new Type[] { typeof(Array), typeof(int), typeof(int) }, typeof(Test));
ILGenerator ilgen = dm.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Castclass, x.GetType());
ilgen.Emit(OpCodes.Ldarg_1); // index
ilgen.Emit(OpCodes.Ldarg_2); // value
ilgen.Emit(OpCodes.Stelem_I4);
ilgen.Emit(OpCodes.Ret);
MySetValue sv = (MySetValue)dm.CreateDelegate(typeof(MySetValue));
sv(x, 2, 300);
delegate void MySetValue(Array array, int index, int value);
The runtime (so is System.Array) supports non-zero lowerbound array (C# does not). The code snippet below creates a length-of-5 integer array with -3 as its' lower-bound, and then tries to set value for some elements.
- We can no longer cast such array to int[].
- Array.SetValue works. Note we need use the lowerbound value to access the first element.
- The instructions "stelem/stelem.<type>" are for one dimensional array with zero-based index only. So the dynamic method approach with stelem instruction won't work.
- For each concrete array type, runtime adds three special methods: Get/Set/Address. With reflection we can hold those MethodInfo, so you can call MethodInfo.Invoke or create another dynamic method wrapper around it. This works for zero-based array too.
// creation: new int[-3..1]
Array x = Array.CreateInstance(typeof(int), new int[] { 5 }, new int[] { -3 });
// 1. late-bound set via Array.SetValue
x.SetValue(200, 1 - 3);
// 3. late-bound set via MethodInfo.Invoke of "Set"
MethodInfo mi = x.GetType().GetMethod("Set");
mi.Invoke(x, new object[] { 3 - 3, 400 });
// 4. late-bound set via DynamicMethod/Delegate/"Set"
DynamicMethod dm = new DynamicMethod("SetValueBySet", typeof(void),
new Type[] { typeof(Array), typeof(int), typeof(int) }, typeof(Test));
ILGenerator ilgen = dm.GetILGenerator();
ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Castclass, x.GetType());
ilgen.Emit(OpCodes.Ldarg_1);
ilgen.Emit(OpCodes.Ldarg_2);
ilgen.Emit(OpCodes.Call, mi);
ilgen.Emit(OpCodes.Ret);
MySetValue sv = (MySetValue)dm.CreateDelegate(typeof(MySetValue));
sv(x, 4 - 3, 500);
Using Vance's CodeTimers class, I measured these 5 approaches' performance on my Vista machine. My scenario is to set value on 1-dimensional zero-based integer array, the action loop contains 10 lines of set-value code (those dynamic method and delegate creation are prepared ahead). The fastest late-bound set approach is DynamicMethod with Stelem, about 90-times slower than the early-bound assignment; but it is 3-times faster than Array.SetValue. Here is the raw output:
Data units of msec resolution = 0.279365 usec
Early-bound assignment : count: 1000000 4.525 +- 1% msec
Late-bound set via Array.SetValue : count: 1000000 1138.182 +- 2% msec
Late-bound set via DynamicMethod/Stelem : count: 1000000 397.005 +- 0% msec
Late-bound set via Set MethodInfo.Invoke: count: 1000000 36352.360 +- 0% msec
Late-bound set via DynamicMethod/Set : count: 1000000 453.956 +- 7% msec
What about multi-dimensional array? Array.SetValue still works; for better performance, we might want use the special "Set" method. By the way, the following ildasm output shows what C# compiler generates for "array[0, 1] = 1000;", which uses "Set". Note "int32[0..., 0...]" is TypeSpec(0x1B).
IL_0009: ldloc.0
IL_000a: ldc.i4.0
IL_000b: ldc.i4.1
IL_000c: ldc.i4 0x3e8
IL_0011: call instance void int32[0...,0...]/* 1B000003 */::Set(int32, int32, int32) /* 0A000020 */