Chapter 18 - 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; }
}
Reflecting Init-only properties
void Main()
{
IsInitOnly (GetType().GetProperty ("Test")).Dump();
}
public int Test { get; init; }
bool IsInitOnly (PropertyInfo pi) => pi
.GetSetMethod().ReturnParameter.GetRequiredCustomModifiers()
.Any (t => t.Name == "IsExternalInit");
Reflecting nullability info
#nullable enable
void Main()
{
PrintPropertyNullability (GetType().GetProperty ("Foo")!);
PrintPropertyNullability (GetType().GetProperty ("Bar")!);
PrintPropertyNullability (GetType().GetProperty ("FooArray")!);
PrintPropertyNullability (GetType().GetProperty ("BarArray")!);
}
public string? Foo { get; set; }
public string Bar { get; set; } = "Bar";
public string?[]? FooArray { get; set; }
public string[] BarArray { get; set; } = new[] { "bar" };
void PrintPropertyNullability (PropertyInfo pi)
{
var info = new NullabilityInfoContext().Create (pi);
info.Dump (pi.Name);
}
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();
}
Calling Static Virtual and Abstract Interface Members
Console.WriteLine (ParseAny (typeof (float), ".2")); // 0.2
MethodInfo GetImplementedInterfaceMethod (Type concreteType, Type interfaceType, string methodName, Type[] paramTypes)
{
var map = concreteType.GetInterfaceMap (interfaceType);
return map.InterfaceMethods
.Zip (map.TargetMethods)
.Single (m => m.First.Name == methodName &&
m.First.GetParameters().Select (p => p.ParameterType)
.SequenceEqual (paramTypes))
.Second;
}
object ParseAny (Type type, string value)
{
MethodInfo parseMethod = GetImplementedInterfaceMethod (type,
type.GetInterface ("IParsable`1"),
"Parse",
new[] { typeof (string), typeof (IFormatProvider) });
return parseMethod.Invoke (null, new[] { value, null });
}
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) + ")";
}
}