Chapter 19 - Reflection and Metadata
Reflecting and Activating Types
Obtaining a Type
void Main() { Type t1 = DateTime.Now.GetType(); // Type obtained at runtime Type t2 = typeof (DateTime); // Type obtained at compile time Type t3 = typeof (DateTime[]); // 1-d Array type Type t4 = typeof (DateTime [,]); // 2-d Array type Type t5 = typeof (Dictionary<int, int>); // Closed generic type Type t6 = typeof (Dictionary<,>); // Unbound generic type t1.Dump(1); t2.Dump(1); t3.Dump(1); t4.Dump(1); t5.Dump(1); t6.Dump(1); Type t = Assembly.GetExecutingAssembly().GetType ("Foo.Bar"); t.Dump(1); } namespace Foo { public class Bar { public int Baz; } }
Simple Type properties
Type stringType = typeof (string); string name = stringType.Name.Dump ("name"); // String Type baseType = stringType.BaseType.Dump ("baseType", 1); // typeof(Object) Assembly assem = stringType.Assembly.Dump ("Assembly"); // System.Private.CoreLib bool isPublic = stringType.IsPublic.Dump ("IsPublic"); // true
Obtaining array types
Type simpleArrayType = typeof (int).MakeArrayType(); Console.WriteLine (simpleArrayType == typeof (int[])); // True //MakeArrayType can be passed an integer argument to make multidimensional rectangular arrays: Type cubeType = typeof (int).MakeArrayType (3); // cube shaped Console.WriteLine (cubeType == typeof (int [,,])); // True //GetElementType does the reverse: it retrieves an array type’s element type: Type e = typeof (int[]).GetElementType().Dump(1); // e == typeof (int) //GetArrayRank returns the number of dimensions of a rectangular array: int rank = typeof (int [,,]).GetArrayRank().Dump ("rank"); // 3
Obtaining nested types
foreach (Type t in typeof (System.Environment).GetNestedTypes()) Console.WriteLine (t.FullName); // The CLR treats a nested type as having special “nested” accessibility levels: Type sf = typeof (System.Environment.SpecialFolder); Console.WriteLine (sf.IsPublic); // False Console.WriteLine (sf.IsNestedPublic); // True
Type Names
void Main() { { Type t = typeof (System.Text.StringBuilder); Console.WriteLine (t.Namespace); // System.Text Console.WriteLine (t.Name); // StringBuilder Console.WriteLine (t.FullName); // System.Text.StringBuilder } // Nested type names { Type t = typeof (System.Environment.SpecialFolder); Console.WriteLine (t.Namespace); // System Console.WriteLine (t.Name); // SpecialFolder Console.WriteLine (t.FullName); // System.Environment+SpecialFolder } // Generic type names { Type t = typeof (Dictionary<,>); // Unbound Console.WriteLine (t.Name); // Dictionary'2 Console.WriteLine (t.FullName); // System.Collections.Generic.Dictionary'2 Console.WriteLine (typeof (Dictionary<int, string>).FullName); } // Array and pointer type names { Console.WriteLine (typeof (int[]).Name); // Int32[] Console.WriteLine (typeof (int [,]).Name); // Int32[,] Console.WriteLine (typeof (int [,]).FullName); // System.Int32[,] } // Pointer types Console.WriteLine (typeof (byte*).Name); // Byte* // ref and out parameter type names int x = 3; RefMethod (ref x); } public void RefMethod (ref int p) { Type t = MethodInfo.GetCurrentMethod().GetParameters() [0].ParameterType; Console.WriteLine (t.Name); // Int32& }
Base Types and Interfaces
Type base1 = typeof (System.String).BaseType; Type base2 = typeof (System.IO.FileStream).BaseType; Console.WriteLine (base1.Name); // Object Console.WriteLine (base2.Name); // Stream Console.WriteLine (); foreach (Type iType in typeof (Guid).GetInterfaces()) Console.WriteLine (iType.Name);
IsInstanceOfType and IsAssignableFrom
object obj = Guid.NewGuid(); Type target = typeof (IFormattable); bool isTrue = obj is IFormattable; // Static C# operator bool alsoTrue = target.IsInstanceOfType (obj); // Dynamic equivalent Debug.Assert (isTrue); Debug.Assert (alsoTrue); Type target2 = typeof (IComparable), source = typeof (string); Console.WriteLine (target2.IsAssignableFrom (source)); // True Console.WriteLine (target2.IsSubclassOf (source)); // False
Instantiating Types
int i = (int)Activator.CreateInstance (typeof (int)).Dump ("i"); DateTime dt = (DateTime)Activator.CreateInstance (typeof (DateTime), 2000, 1, 1); dt.Dump ("dt");
Instantiating Types - with ConstructorInfo
void Main() { // Fetch the constructor that accepts a single parameter of type string: ConstructorInfo ci = typeof (X).GetConstructor (new[] { typeof (string) }); // Construct the object using that overload, passing in null: object foo = ci.Invoke (new object[] { null }); foo.Dump(); } class X { public X (string s) { } public X (StringBuilder sb) { } }
Instantiating Types - arrays
class Program { delegate int IntFunc (int x); static int Square (int x) => x * x; // Static method int Cube (int x) => x * x * x; // Instance method static void Main() { Delegate staticD = Delegate.CreateDelegate (typeof (IntFunc), typeof (Program), "Square"); Delegate instanceD = Delegate.CreateDelegate (typeof (IntFunc), new Program(), "Cube"); Console.WriteLine (staticD.DynamicInvoke (3)); // 9 Console.WriteLine (instanceD.DynamicInvoke (3)); // 27 IntFunc f = (IntFunc)staticD; Console.WriteLine (f (3)); // 9 (but much faster!) } }
Generic Types
Type closed = typeof (List<int>); List<int> list = (List<int>) Activator.CreateInstance (closed); // OK Type unbound = typeof (List<>).Dump ("unbound", 1); try { object anError = Activator.CreateInstance (unbound); // Runtime error } catch (Exception ex) { ex.Dump ("You cannot instantiate an unbound type"); } // The MakeGenericType method converts an unbound into a closed generic type. closed = unbound.MakeGenericType (typeof (int)).Dump ("closed", 1); //The GetGenericTypeDefinition method does the opposite: Type unbound2 = closed.GetGenericTypeDefinition(); // unbound == unbound2 // The IsGenericType property returns true if a Type is generic, and the // IsGenericTypeDefinition property returns true if the generic type is unbound. // The following tests whether a type is a nullable value type: Type nullable = typeof (bool?); Console.WriteLine ( nullable.IsGenericType && nullable.GetGenericTypeDefinition() == typeof (Nullable<>)); // True //GetGenericArguments returns the type arguments for closed generic types: Console.WriteLine (closed.GetGenericArguments() [0]); // System.Int32 Console.WriteLine (nullable.GetGenericArguments() [0]); // System.Boolean // For unbound generic types, GetGenericArguments returns pseudotypes that // represent the placeholder types specified in the generic type definition: Console.WriteLine (unbound.GetGenericArguments() [0]); // T
Reflecting and Invoking Members
Getting public members
void Main() { MemberInfo[] members = typeof (Walnut).GetMembers(); foreach (MemberInfo m in members) Console.WriteLine (m); } class Walnut { private bool cracked; public void Crack() { cracked = true; } }
DeclaringType vs ReflectedType
class Program { static void Main() { // MethodInfo is a subclass of MemberInfo; see Figure 19-1. MethodInfo test = typeof (Program).GetMethod ("ToString"); MethodInfo obj = typeof (object).GetMethod ("ToString"); Console.WriteLine (test.DeclaringType); // System.Object Console.WriteLine (obj.DeclaringType); // System.Object Console.WriteLine (test.ReflectedType); // Program Console.WriteLine (obj.ReflectedType); // System.Object Console.WriteLine (test == obj); // False Console.WriteLine (test.MethodHandle == obj.MethodHandle); // True Console.WriteLine (test.MetadataToken == obj.MetadataToken // True && test.Module == obj.Module); } }
C# members vs CLR members
void Main() { foreach (MethodInfo mi in typeof (Test).GetMethods()) Console.Write (mi.Name + " "); PropertyInfo pi = typeof (Console).GetProperty ("Title").Dump ("Property"); MethodInfo getter = pi.GetGetMethod().Dump ("Get Method"); // get_Title MethodInfo setter = pi.GetSetMethod().Dump ("Set Method"); // set_Title MethodInfo[] both = pi.GetAccessors().Dump ("Accessors"); // Length==2 PropertyInfo p = getter.DeclaringType.GetProperties() .First (x => x.GetAccessors (true).Contains (getter)); Debug.Assert (p == pi); } class Test { public int X { get { return 0; } set { } } } class Walnut { private bool cracked; public void Crack() { cracked = true; } }
Generic Type Members
PropertyInfo unbound = typeof (IEnumerator<>).GetProperty ("Current"); PropertyInfo closed = typeof (IEnumerator<int>).GetProperty ("Current"); Console.WriteLine (unbound); // T Current Console.WriteLine (closed); // Int32 Current Console.WriteLine (unbound.PropertyType.IsGenericParameter); // True Console.WriteLine (closed.PropertyType.IsGenericParameter); // False
Generic Type Members - unbound vs closed
// The MemberInfo objects returned from unbound and closed generic types are always distinct // — even for members whose signatures don’t feature generic type parameters: PropertyInfo unbound = typeof (List<>).GetProperty ("Count"); PropertyInfo closed = typeof (List<int>).GetProperty ("Count"); Console.WriteLine (unbound); // Int32 Count Console.WriteLine (closed); // Int32 Count Console.WriteLine (unbound == closed); // False Console.WriteLine (unbound.DeclaringType.IsGenericTypeDefinition); // True Console.WriteLine (closed.DeclaringType.IsGenericTypeDefinition); // False
Dynamically invoking a member
object s = "Hello"; PropertyInfo prop = s.GetType().GetProperty ("Length"); int length = (int)prop.GetValue (s, null); // 5 length.Dump();
Method Parameters
Type type = typeof (string); Type[] parameterTypes = { typeof (int) }; MethodInfo method = type.GetMethod ("Substring", parameterTypes); object[] arguments = { 2 }; object returnValue = method.Invoke ("stamp", arguments); Console.WriteLine (returnValue); // "amp" ParameterInfo[] paramList = method.GetParameters(); foreach (ParameterInfo x in paramList) { Console.WriteLine (x.Name); // startIndex Console.WriteLine (x.ParameterType); // System.Int32 }
Method Parameters - ref and out
object[] args = { "23", 0 }; Type[] argTypes = { typeof (string), typeof (int).MakeByRefType() }; MethodInfo tryParse = typeof (int).GetMethod ("TryParse", argTypes); bool successfulParse = (bool) tryParse.Invoke (null, args); Console.WriteLine (successfulParse + " " + args [1]); // True 23
Method Parameters - with generic methods
var unboundMethod = ( from m in typeof (Enumerable).GetMethods() where m.Name == "Where" && m.IsGenericMethod let parameters = m.GetParameters() where parameters.Length == 2 let genArg = m.GetGenericArguments().First() let enumerableOfT = typeof (IEnumerable<>).MakeGenericType (genArg) let funcOfTBool = typeof (Func<,>).MakeGenericType (genArg, typeof (bool)) where parameters [0].ParameterType == enumerableOfT && parameters [1].ParameterType == funcOfTBool select m).Single(); unboundMethod.Dump ("Unbound method"); var closedMethod = unboundMethod.MakeGenericMethod (typeof (int)) .Dump ("Closed method"); int[] source = { 3, 4, 5, 6, 7, 8 }; Func<int, bool> predicate = n => n % 2 == 1; // Odd numbers only // We can now invoke the closed generic method as follows: var query = (IEnumerable<int>)closedMethod.Invoke (null, new object[] { source, predicate }); query.Dump();
Using Delegates for Performance
delegate string StringToString (string s); static void Main() { MethodInfo trimMethod = typeof (string).GetMethod ("Trim", new Type[0]); // First let's test the performance when calling Invoke var sw = Stopwatch.StartNew(); for (int i = 0; i < 1000000; i++) trimMethod.Invoke ("test", null); sw.Stop(); sw.Dump ("Using Invoke"); // Now let's test the performance when using a delegate: var trim = (StringToString) Delegate.CreateDelegate (typeof (StringToString), trimMethod); sw.Restart(); for (int i = 0; i < 1000000; i++) trim ("test"); sw.Stop(); sw.Dump ("Using a delegate"); }
Accessing Non-Public Members
void Main() { Type t = typeof (Walnut); Walnut w = new Walnut(); w.Crack(); FieldInfo f = t.GetField ("cracked", BindingFlags.NonPublic | BindingFlags.Instance); f.SetValue (w, false); Console.WriteLine (w); // False } class Walnut { private bool cracked; public void Crack() { cracked = true; } public override string ToString() { return cracked.ToString(); } }
Accessing Non-Public Members - BindingFlags
BindingFlags publicStatic = BindingFlags.Public | BindingFlags.Static; MemberInfo[] members = typeof (object).GetMembers (publicStatic); members.Dump ("Public members", 1); // The following example retrieves all the nonpublic members of type object, both static and instance: BindingFlags nonPublicBinding = BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; MemberInfo[] nonPublic = typeof (object).GetMembers (nonPublicBinding); nonPublic.Dump ("Non-public members", 1);
Invoking Generic Methods
class Program { public static T Echo<T> (T x) { return x; } static void Main() { MethodInfo echo = typeof (Program).GetMethod ("Echo"); Console.WriteLine (echo.IsGenericMethodDefinition); // True try { echo.Invoke (null, new object[] { 123 }); // Exception } catch (Exception ex) { ex.Dump ("This can't be done"); } MethodInfo intEcho = echo.MakeGenericMethod (typeof (int)); Console.WriteLine (intEcho.IsGenericMethodDefinition); // False Console.WriteLine (intEcho.Invoke (null, new object[] { 3 })); // 3 } }
Anonymously Calling Members of a Generic Interface
void Main() { Console.WriteLine (ToStringEx (new List<int> { 5, 6, 7 })); Console.WriteLine (ToStringEx ("xyyzzz".GroupBy (c => c))); } public static string ToStringEx (object value) { if (value == null) return "<null>"; if (value.GetType().IsPrimitive) return value.ToString(); StringBuilder sb = new StringBuilder(); if (value is IList) sb.Append ("List of " + ((IList)value).Count + " items: "); Type closedIGrouping = value.GetType().GetInterfaces() .Where (t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof (IGrouping<,>)) .FirstOrDefault(); if (closedIGrouping != null) // Call the Key property on IGrouping<,> { PropertyInfo pi = closedIGrouping.GetProperty ("Key"); object key = pi.GetValue (value, null); sb.Append ("Group with key=" + key + ": "); } if (value is IEnumerable) foreach (object element in ((IEnumerable)value)) sb.Append (ToStringEx (element) + " "); if (sb.Length == 0) sb.Append (value.ToString()); return "\r\n" + sb.ToString(); }
Reflecting Assemblies
Reflecting Assemblies
void Main() { Assembly currentAssem = Assembly.GetExecutingAssembly(); var t = currentAssem.GetType ("Demos.TestProgram"); t.Dump(); var allTypes = currentAssem.GetTypes().Dump(); } namespace Demos { class TestProgram { } class SomeOtherType { } }
Working with Attributes
Bit-mapped attributes
TypeAttributes ta = typeof (Console).Attributes.Dump(); MethodAttributes ma = MethodInfo.GetCurrentMethod().Attributes.Dump();
Defining Your Own Attribute
void Main() { } [AttributeUsage (AttributeTargets.Method)] public sealed class TestAttribute : Attribute { public int Repetitions; public string FailureMessage; public TestAttribute () : this (1) { } public TestAttribute (int repetitions) { Repetitions = repetitions; } } class Foo { [Test] public void Method1() { } [Test (20)] public void Method2() { } [Test (20, FailureMessage = "Debugging Time!")] public void Method3() { } }
Retrieving Attributes at Runtime
void Main() { foreach (MethodInfo mi in typeof (Foo).GetMethods()) { TestAttribute att = (TestAttribute)Attribute.GetCustomAttribute (mi, typeof (TestAttribute)); if (att != null) Console.WriteLine ("Method {0} will be tested; reps={1}; msg={2}", mi.Name, att.Repetitions, att.FailureMessage); } } [AttributeUsage (AttributeTargets.Method)] public sealed class TestAttribute : Attribute { public int Repetitions; public string FailureMessage; public TestAttribute () : this (1) { } public TestAttribute (int repetitions) { Repetitions = repetitions; } } class Foo { [Test] public void Method1() { } [Test (20)] public void Method2() { } [Test (20, FailureMessage = "Debugging Time!")] public void Method3() { } }
Retrieving Attributes at Runtime - Unit Test example
void Main() { foreach (MethodInfo mi in typeof (Foo).GetMethods()) { TestAttribute att = (TestAttribute)Attribute.GetCustomAttribute (mi, typeof (TestAttribute)); if (att != null) for (int i = 0; i < att.Repetitions; i++) try { mi.Invoke (new Foo(), null); // Call method with no arguments $"Successfully called {mi.Name}".Dump(); } catch (Exception ex) // Wrap exception in att.FailureMessage { throw new Exception ("Error: " + att.FailureMessage, ex); } } } [AttributeUsage (AttributeTargets.Method)] public sealed class TestAttribute : Attribute { public int Repetitions; public string FailureMessage; public TestAttribute () : this (1) { } public TestAttribute (int repetitions) { Repetitions = repetitions; } } class Foo { [Test] public void Method1() { } [Test (20)] public void Method2() { } [Test (20, FailureMessage = "Debugging Time!")] public void Method3() { } }
Retrieving Attributes on a Specific Type
[Serializable, Obsolete] class Test { static void Main() { object[] atts = Attribute.GetCustomAttributes (typeof (Test)); foreach (object att in atts) Console.WriteLine (att); } }
Dynamic Code Generation (Reflection.Emit)
Generating IL with DynamicMethod
void Main() { var dynMeth = new DynamicMethod ("Foo", null, null, typeof (Test)); ILGenerator gen = dynMeth.GetILGenerator(); gen.EmitWriteLine ("Hello world"); gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null); // Hello world } public class Test { }
Generating IL with DynamicMethod - nonpublic access
void Main() { var dynMeth = new DynamicMethod ("Foo", null, null, typeof (Test)); ILGenerator gen = dynMeth.GetILGenerator(); MethodInfo privateMethod = typeof (Test).GetMethod ("HelloWorld", BindingFlags.Static | BindingFlags.NonPublic); gen.Emit (OpCodes.Call, privateMethod); // Call HelloWorld gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null); // Hello world } public class Test { static void HelloWorld() // private method, yet we can call it { Console.WriteLine ("Hello world"); } }
The Evaluation Stack
var dynMeth = new DynamicMethod ("Foo", null, null, typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); MethodInfo writeLineInt = typeof (Console).GetMethod ("WriteLine", new Type[] { typeof (int) }); // The Ldc* op-codes load numeric literals of various types and sizes. gen.Emit (OpCodes.Ldc_I4, 123); // Push a 4-byte integer onto stack gen.Emit (OpCodes.Call, writeLineInt); gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null); // 123
The Evaluation Stack - 2 + 2
var dynMeth = new DynamicMethod ("Foo", null, null, typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); MethodInfo writeLineInt = typeof (Console).GetMethod ("WriteLine", new Type[] { typeof (int) }); // Calculate 2 + 2 gen.Emit (OpCodes.Ldc_I4, 2); // Push a 4-byte integer, value=2 gen.Emit (OpCodes.Ldc_I4, 2); // Push a 4-byte integer, value=2 gen.Emit (OpCodes.Add); // Add the result together gen.Emit (OpCodes.Call, writeLineInt); gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null);
The Evaluation Stack - 10 over 2 + 1
var dynMeth = new DynamicMethod ("Foo", null, null, typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); MethodInfo writeLineInt = typeof (Console).GetMethod ("WriteLine", new Type[] { typeof (int) }); // Calculate 10 / 2 + 1: gen.Emit (OpCodes.Ldc_I4, 10); gen.Emit (OpCodes.Ldc_I4, 2); gen.Emit (OpCodes.Div); gen.Emit (OpCodes.Ldc_I4, 1); gen.Emit (OpCodes.Add); gen.Emit (OpCodes.Call, writeLineInt); // Here's another way to do the same thing: gen.Emit (OpCodes.Ldc_I4, 1); gen.Emit (OpCodes.Ldc_I4, 10); gen.Emit (OpCodes.Ldc_I4, 2); gen.Emit (OpCodes.Div); gen.Emit (OpCodes.Add); gen.Emit (OpCodes.Call, writeLineInt); gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null);
Passing Arguments to a DynamicMethod
DynamicMethod dynMeth = new DynamicMethod ("Foo", typeof (int), // Return type = int new[] { typeof (int), typeof (int) }, // Parameter types = int, int typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); gen.Emit (OpCodes.Ldarg_0); // Push first arg onto eval stack gen.Emit (OpCodes.Ldarg_1); // Push second arg onto eval stack gen.Emit (OpCodes.Add); // Add them together (result on stack) gen.Emit (OpCodes.Ret); // Return with stack having 1 value int result = (int)dynMeth.Invoke (null, new object[] { 3, 4 }); // 7 result.Dump(); // If you need to invoke the method repeatedly, here's an optimized solution: var func = (Func<int,int,int>)dynMeth.CreateDelegate (typeof (Func<int,int,int>)); result = func (3, 4); // 7 result.Dump();
Generating Local Variables
//int x = 6; //int y = 7; //x *= y; //Console.WriteLine (x); var dynMeth = new DynamicMethod ("Test", null, null, typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); LocalBuilder localX = gen.DeclareLocal (typeof (int)); // Declare x LocalBuilder localY = gen.DeclareLocal (typeof (int)); // Declare y gen.Emit (OpCodes.Ldc_I4, 6); // Push literal 6 onto eval stack gen.Emit (OpCodes.Stloc, localX); // Store in localX gen.Emit (OpCodes.Ldc_I4, 7); // Push literal 7 onto eval stack gen.Emit (OpCodes.Stloc, localY); // Store in localY gen.Emit (OpCodes.Ldloc, localX); // Push localX onto eval stack gen.Emit (OpCodes.Ldloc, localY); // Push localY onto eval stack gen.Emit (OpCodes.Mul); // Multiply values together gen.Emit (OpCodes.Stloc, localX); // Store the result to localX gen.EmitWriteLine (localX); // Write the value of localX gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null); // 42
Branching
//int x = 5; //while (x <= 10) Console.WriteLine (x++); var dynMeth = new DynamicMethod ("Test", null, null, typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); Label startLoop = gen.DefineLabel(); // Declare labels Label endLoop = gen.DefineLabel(); LocalBuilder x = gen.DeclareLocal (typeof (int)); // int x gen.Emit (OpCodes.Ldc_I4, 5); // gen.Emit (OpCodes.Stloc, x); // x = 5 gen.MarkLabel (startLoop); { gen.Emit (OpCodes.Ldc_I4, 10); // Load 10 onto eval stack gen.Emit (OpCodes.Ldloc, x); // Load x onto eval stack gen.Emit (OpCodes.Blt, endLoop); // if (x > 10) goto endLoop gen.EmitWriteLine (x); // Console.WriteLine (x) gen.Emit (OpCodes.Ldloc, x); // Load x onto eval stack gen.Emit (OpCodes.Ldc_I4, 1); // Load 1 onto the stack gen.Emit (OpCodes.Add); // Add them together gen.Emit (OpCodes.Stloc, x); // Save result back to x gen.Emit (OpCodes.Br, startLoop); // return to start of loop } gen.MarkLabel (endLoop); gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null);
Instantiating Objects and Calling Instance Methods
var dynMeth = new DynamicMethod ("Test", null, null, typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); ConstructorInfo ci = typeof (StringBuilder).GetConstructor (new Type [0]); gen.Emit (OpCodes.Newobj, ci); gen.Emit (OpCodes.Callvirt, typeof (StringBuilder) .GetProperty ("MaxCapacity").GetGetMethod()); gen.Emit (OpCodes.Call, typeof (Console).GetMethod ("WriteLine", new[] { typeof (int) } )); gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null); // 2147483647
Appending to a StringBuilder
var dynMeth = new DynamicMethod ("Test", null, null, typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); // We will call: new StringBuilder ("Hello", 1000) ConstructorInfo ci = typeof (StringBuilder).GetConstructor ( new[] { typeof (string), typeof (int) } ); gen.Emit (OpCodes.Ldstr, "Hello"); // Load a string onto the eval stack gen.Emit (OpCodes.Ldc_I4, 1000); // Load an int onto the eval stack gen.Emit (OpCodes.Newobj, ci); // Construct the StringBuilder Type[] strT = { typeof (string) }; gen.Emit (OpCodes.Ldstr, ", world!"); gen.Emit (OpCodes.Call, typeof (StringBuilder).GetMethod ("Append", strT)); gen.Emit (OpCodes.Callvirt, typeof (object).GetMethod ("ToString")); gen.Emit (OpCodes.Call, typeof (Console).GetMethod ("WriteLine", strT)); gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null); // Hello, world!
Exception Handling
var dynMeth = new DynamicMethod ("Test", null, null, typeof (void)); ILGenerator gen = dynMeth.GetILGenerator(); // try { throw new NotSupportedException(); } // catch (NotSupportedException ex) { Console.WriteLine (ex.Message); } // finally { Console.WriteLine ("Finally"); } MethodInfo getMessageProp = typeof (NotSupportedException) .GetProperty ("Message").GetGetMethod(); MethodInfo writeLineString = typeof (Console).GetMethod ("WriteLine", new[] { typeof (object) } ); gen.BeginExceptionBlock(); { ConstructorInfo ci = typeof (NotSupportedException).GetConstructor ( new Type[0] ); gen.Emit (OpCodes.Newobj, ci); gen.Emit (OpCodes.Throw); } gen.BeginCatchBlock (typeof (NotSupportedException)); { gen.Emit (OpCodes.Callvirt, getMessageProp); gen.Emit (OpCodes.Call, writeLineString); } gen.BeginFinallyBlock(); { gen.EmitWriteLine ("Finally"); } gen.EndExceptionBlock(); gen.Emit (OpCodes.Ret); dynMeth.Invoke (null, null); // Hello, world!
Emitting Assemblies and Types
Emitting Assemblies and Types
AssemblyName aname = new AssemblyName ("MyDynamicAssembly"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly (aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("DynModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); MethodBuilder methBuilder = tb.DefineMethod ("SayHello", MethodAttributes.Public, null, null); ILGenerator gen = methBuilder.GetILGenerator(); gen.EmitWriteLine ("Hello world"); gen.Emit (OpCodes.Ret); // Create the type, finalizing its definition: Type t = tb.CreateType(); // Once the type is created, we use ordinary reflection to inspect // and perform dynamic binding: object o = Activator.CreateInstance (t); t.GetMethod ("SayHello").Invoke (o, null); // Hello world
Emitting Type Members
Emitting Methods
// public static double SquareRoot (double value) => Math.Sqrt (value); AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); MethodBuilder mb = tb.DefineMethod ("SquareRoot", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof (double), // Return type new[] { typeof (double) }); // Parameter types mb.DefineParameter (1, ParameterAttributes.None, "value"); // Assign name ILGenerator gen = mb.GetILGenerator(); gen.Emit (OpCodes.Ldarg_0); // Load 1st arg gen.Emit (OpCodes.Call, typeof (Math).GetMethod ("Sqrt")); gen.Emit (OpCodes.Ret); Type realType = tb.CreateType(); double x = (double)tb.GetMethod ("SquareRoot").Invoke (null, new object[] { 10.0 }); Console.WriteLine (x); // 3.16227766016838 // LINQPad can disassemble methods for you: tb.GetMethod ("SquareRoot").Disassemble().Dump ("LINQPad disassembly");
Emitting Methods - ref and out
// public static void SquareRoot (ref double value) => value = Math.Sqrt (value); AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); MethodBuilder mb = tb.DefineMethod ("SquareRoot", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, null, new Type[] { typeof (double).MakeByRefType() } ); // For an 'out' parameter, use ParameterAttributes.Out instead: mb.DefineParameter (1, ParameterAttributes.None, "value"); ILGenerator gen = mb.GetILGenerator(); gen.Emit (OpCodes.Ldarg_0); gen.Emit (OpCodes.Ldarg_0); gen.Emit (OpCodes.Ldind_R8); gen.Emit (OpCodes.Call, typeof (Math).GetMethod ("Sqrt")); gen.Emit (OpCodes.Stind_R8); gen.Emit (OpCodes.Ret); Type realType = tb.CreateType(); object[] args = { 10.0 }; tb.GetMethod ("SquareRoot").Invoke (null, args); Console.WriteLine (args [0]); // 3.16227766016838 // LINQPad can disassemble methods for you: tb.GetMethod ("SquareRoot").Disassemble().Dump ("LINQPad disassembly");
Emitting Fields and Properties
// string _text; // public string Text // { // get => _text; // internal set => _text = value; // } AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); FieldBuilder field = tb.DefineField ("_text", typeof (string), FieldAttributes.Private); PropertyBuilder prop = tb.DefineProperty ( "Text", // Name of property PropertyAttributes.None, typeof (string), // Property type new Type[0]); // Indexer types MethodBuilder getter = tb.DefineMethod ( "get_Text", // Method name MethodAttributes.Public | MethodAttributes.SpecialName, typeof (string), // Return type new Type [0]); // Parameter types ILGenerator getGen = getter.GetILGenerator(); getGen.Emit (OpCodes.Ldarg_0); // Load "this" onto eval stack getGen.Emit (OpCodes.Ldfld, field); // Load field value onto eval stack getGen.Emit (OpCodes.Ret); // Return MethodBuilder setter = tb.DefineMethod ( "set_Text", MethodAttributes.Assembly | MethodAttributes.SpecialName, null, // Return type new Type[] { typeof (string) }); // Parameter types ILGenerator setGen = setter.GetILGenerator(); setGen.Emit (OpCodes.Ldarg_0); // Load "this" onto eval stack setGen.Emit (OpCodes.Ldarg_1); // Load 2nd arg, i.e., value setGen.Emit (OpCodes.Stfld, field); // Store value into field setGen.Emit (OpCodes.Ret); // return prop.SetGetMethod (getter); // Link the get method and property prop.SetSetMethod (setter); // Link the set method and property // Test it: Type t = tb.CreateType(); object o = Activator.CreateInstance (t); t.GetProperty ("Text").SetValue (o, "Good emissions!", new object [0]); string text = (string)t.GetProperty ("Text").GetValue (o, null); Console.WriteLine (text); // Good emissions!
Emitting Constructors
// class Widget // { // int _capacity = 4000; // } AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); FieldBuilder field = tb.DefineField ("_capacity", typeof (int), FieldAttributes.Private); ConstructorBuilder c = tb.DefineConstructor ( MethodAttributes.Public, CallingConventions.Standard, new Type[0]); // Constructor parameters ILGenerator gen = c.GetILGenerator(); gen.Emit (OpCodes.Ldarg_0); // Load "this" onto eval stack gen.Emit (OpCodes.Ldc_I4, 4000); // Load 4000 onto eval stack gen.Emit (OpCodes.Stfld, field); // Store it to our field gen.Emit (OpCodes.Ret); Type t = tb.CreateType(); t.GetConstructors().Single().Disassemble().Dump ("LINQPad disassembly");
Attaching Attributes
// [XmlElement ("FirstName", Namespace="http://test/", Order=3)] AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); Type attType = typeof (XmlElementAttribute); ConstructorInfo attConstructor = attType.GetConstructor ( new Type[] { typeof (string) } ); var att = new CustomAttributeBuilder ( attConstructor, // Constructor new object[] { "FirstName" }, // Constructor arguments new PropertyInfo[] { attType.GetProperty ("Namespace"), // Properties attType.GetProperty ("Order") }, new object[] { "http://test/", 3 } // Property values ); FieldBuilder myFieldBuilder = tb.DefineField ("SomeField", typeof (string), FieldAttributes.Public); myFieldBuilder.SetCustomAttribute (att); // or propBuilder.SetCustomAttribute (att); // or typeBuilder.SetCustomAttribute (att); etc Type t = tb.CreateType(); t.GetField ("SomeField").GetCustomAttributes().Dump();
Emitting Generic Methods and Types
Defining Generic Methods
// public static T Echo<T> (T value) // { // return value; // } AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); MethodBuilder mb = tb.DefineMethod ("Echo", MethodAttributes.Public | MethodAttributes.Static); GenericTypeParameterBuilder[] genericParams = mb.DefineGenericParameters ("T"); mb.SetSignature (genericParams [0], // Return type null, null, genericParams, // Parameter types null, null); mb.DefineParameter (1, ParameterAttributes.None, "value"); // Optional ILGenerator gen = mb.GetILGenerator(); gen.Emit (OpCodes.Ldarg_0); gen.Emit (OpCodes.Ret);
Defining Generic Types
// public class Widget<T> // { // public T Value; // } AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); GenericTypeParameterBuilder[] genericParams = tb.DefineGenericParameters ("T"); tb.DefineField ("Value", genericParams [0], FieldAttributes.Public);
Awkward Emission Targets
Uncreated Closed Generics
// public class Widget // { // public static void Test() // { // var list = new List<Widget>(); // } // } AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); TypeBuilder tb = modBuilder.DefineType ("Widget", TypeAttributes.Public); MethodBuilder mb = tb.DefineMethod ("Test", MethodAttributes.Public | MethodAttributes.Static); ILGenerator gen = mb.GetILGenerator(); Type variableType = typeof (List<>).MakeGenericType (tb); ConstructorInfo unbound = typeof (List<>).GetConstructor (new Type [0]); ConstructorInfo ci = TypeBuilder.GetConstructor (variableType, unbound); LocalBuilder listVar = gen.DeclareLocal (variableType); gen.Emit (OpCodes.Newobj, ci); gen.Emit (OpCodes.Stloc, listVar); gen.Emit (OpCodes.Ret);
Circular Dependencies - Problem
// class A { S<B> Bee; } // class B { S<A> Aye; } void Main() { AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); var pub = FieldAttributes.Public; TypeBuilder aBuilder = modBuilder.DefineType ("A"); TypeBuilder bBuilder = modBuilder.DefineType ("B"); aBuilder.DefineField ("Bee", typeof (S<>).MakeGenericType (bBuilder), pub); bBuilder.DefineField ("Aye", typeof (S<>).MakeGenericType (aBuilder), pub); Type realA = aBuilder.CreateType(); // TypeLoadException: cannot load type B Type realB = bBuilder.CreateType(); } public struct S<T> { public T SomeField; }
Circular Dependencies - Solution
// class A { S<B> Bee; } // class B { S<A> Aye; } void Main() { AssemblyName aname = new AssemblyName ("MyEmissions"); AssemblyBuilder assemBuilder = AssemblyBuilder.DefineDynamicAssembly ( aname, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemBuilder.DefineDynamicModule ("MainModule"); var pub = FieldAttributes.Public; TypeBuilder aBuilder = modBuilder.DefineType ("A"); TypeBuilder bBuilder = modBuilder.DefineType ("B"); aBuilder.DefineField ("Bee", typeof (S<>).MakeGenericType (bBuilder), pub); bBuilder.DefineField ("Aye", typeof (S<>).MakeGenericType (aBuilder), pub); TypeBuilder[] uncreatedTypes = { aBuilder, bBuilder }; ResolveEventHandler handler = delegate (object o, ResolveEventArgs args) { var type = uncreatedTypes.FirstOrDefault (t => t.FullName == args.Name); return type == null ? null : type.CreateType().Assembly; }; AppDomain.CurrentDomain.TypeResolve += handler; Type realA = aBuilder.CreateType(); Type realB = bBuilder.CreateType(); AppDomain.CurrentDomain.TypeResolve -= handler; } public struct S<T> { public T SomeField; }
Parsing IL
Writing a Disassembler
void Main() { MethodInfo mi = typeof (Disassembler).GetMethod ( "ReadOperand", BindingFlags.Instance | BindingFlags.NonPublic); Console.WriteLine (Disassembler.Disassemble (mi)); } public class Disassembler { public static string Disassemble (MethodBase method) => new Disassembler (method).Dis(); static Dictionary<short, OpCode> _opcodes = new Dictionary<short, OpCode>(); static Disassembler() { Dictionary<short, OpCode> opcodes = new Dictionary<short, OpCode>(); foreach (FieldInfo fi in typeof (OpCodes).GetFields (BindingFlags.Public | BindingFlags.Static)) if (typeof (OpCode).IsAssignableFrom (fi.FieldType)) { OpCode code = (OpCode)fi.GetValue (null); // Get field's value if (code.OpCodeType != OpCodeType.Nternal) _opcodes.Add (code.Value, code); } } StringBuilder _output; // The result to which we'll keep appending Module _module; // This will come in handy later byte[] _il; // The raw byte code int _pos; // The position we're up to in the byte code Disassembler (MethodBase method) { _module = method.DeclaringType.Module; _il = method.GetMethodBody().GetILAsByteArray(); } string Dis() { _output = new StringBuilder(); while (_pos < _il.Length) DisassembleNextInstruction(); return _output.ToString(); } void DisassembleNextInstruction() { int opStart = _pos; OpCode code = ReadOpCode(); string operand = ReadOperand (code); _output.AppendFormat ("IL_{0:X4}: {1,-12} {2}", opStart, code.Name, operand); _output.AppendLine(); } OpCode ReadOpCode() { byte byteCode = _il [_pos++]; if (_opcodes.ContainsKey (byteCode)) return _opcodes [byteCode]; if (_pos == _il.Length) throw new Exception ("Unexpected end of IL"); short shortCode = (short)(byteCode * 256 + _il [_pos++]); if (!_opcodes.ContainsKey (shortCode)) throw new Exception ("Cannot find opcode " + shortCode); return _opcodes [shortCode]; } string ReadOperand (OpCode c) { int operandLength = c.OperandType == OperandType.InlineNone ? 0 : c.OperandType == OperandType.ShortInlineBrTarget || c.OperandType == OperandType.ShortInlineI || c.OperandType == OperandType.ShortInlineVar ? 1 : c.OperandType == OperandType.InlineVar ? 2 : c.OperandType == OperandType.InlineI8 || c.OperandType == OperandType.InlineR ? 8 : c.OperandType == OperandType.InlineSwitch ? 4 * (BitConverter.ToInt32 (_il, _pos) + 1) : 4; // All others are 4 bytes if (_pos + operandLength > _il.Length) throw new Exception ("Unexpected end of IL"); string result = FormatOperand (c, operandLength); if (result == null) { // Write out operand bytes in hex result = ""; for (int i = 0; i < operandLength; i++) result += _il [_pos + i].ToString ("X2") + " "; } _pos += operandLength; return result; } string FormatOperand (OpCode c, int operandLength) { if (operandLength == 0) return ""; if (operandLength == 4) return Get4ByteOperand (c); else if (c.OperandType == OperandType.ShortInlineBrTarget) return GetShortRelativeTarget(); else if (c.OperandType == OperandType.InlineSwitch) return GetSwitchTarget (operandLength); else return null; } string Get4ByteOperand (OpCode c) { int intOp = BitConverter.ToInt32 (_il, _pos); switch (c.OperandType) { case OperandType.InlineTok: case OperandType.InlineMethod: case OperandType.InlineField: case OperandType.InlineType: MemberInfo mi; try { mi = _module.ResolveMember (intOp); } catch { return null; } if (mi == null) return null; if (mi.ReflectedType != null) return mi.ReflectedType.FullName + "." + mi.Name; else if (mi is Type) return ((Type)mi).FullName; else return mi.Name; case OperandType.InlineString: string s = _module.ResolveString (intOp); if (s != null) s = "'" + s + "'"; return s; case OperandType.InlineBrTarget: return "IL_" + (_pos + intOp + 4).ToString ("X4"); default: return null; } } string GetShortRelativeTarget() { int absoluteTarget = _pos + (sbyte)_il [_pos] + 1; return "IL_" + absoluteTarget.ToString ("X4"); } string GetSwitchTarget (int operandLength) { int targetCount = BitConverter.ToInt32 (_il, _pos); string [] targets = new string [targetCount]; for (int i = 0; i < targetCount; i++) { int ilTarget = BitConverter.ToInt32 (_il, _pos + (i + 1) * 4); targets [i] = "IL_" + (_pos + ilTarget + operandLength).ToString ("X4"); } return "(" + string.Join (", ", targets) + ")"; } }