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) + ")";
  }
}
C# 12 in a Nutshell
Buy from amazon.com Buy print or Kindle edition
Buy from ebooks.com Buy PDF edition
Buy from O'Reilly Read via O'Reilly subscription