Thursday, April 19, 2012

Dynamic Types and Assemblies in .NET

Here is a tinker I did with the .net reflection library to do some dynamic type generation. There are testing mock libraries out there that do this and are way more robust, but being what it is I thought I would post it in case anyone else became curious about this. Prepare for a huge code dump below!


using System;
using
 System.Collections.Generic;
using
 System.Linq;
using
 System.Reflection;
using
 System.Reflection.Emit;
namespace
 Dynamic
{
    
/// <summary>
    
/// Represents a virtual assembly in memory
    
/// </summary>
    
public class Namespace : IDisposable
    {
        
private readonly AssemblyName _assemblyName;
        
private readonly AssemblyBuilder _assemblyBuilder;
        
private readonly ModuleBuilder _moduleBuilder;

        
private readonly List<Type> _classes = new List<Type>();
        
/// <summary>
        
/// A collection of classes belonging to this dynamic namespace
        
/// </summary>
        
public Type[] Classes { get { return _classes.ToArray(); } }

        
private readonly List<Type> _interfaces = new List<Type>();
        
/// <summary>
        
/// A collection of interfaces belonging to this dynamic namespace
        
/// </summary>
        
public Type[] Interfaces { get { return _interfaces.ToArray(); } }

        
private readonly List<Type> _enums = new List<Type>();
        
/// <summary>
        
/// A collection of enums belonging to this dynamic namespace
        
/// </summary>
        
public Type[] Enums { get { return _enums.ToArray(); } }

        
/// <summary>
        
/// Creates a new dynamic namespace for containing simple dynamic types
        
/// </summary>
        
public Namespace()
        {
            _assemblyName = 
new AssemblyName(Guid.NewGuid().ToString());
            
// Assembly builder MUST be set to run and collect to be disposable. The dynamic assembly will participate in normal garbage collection when all references to it and its types are released.
            
// REF: http://msdn.microsoft.com/en-us/library/dd554932.aspx
            _assemblyBuilder = 
AppDomain.CurrentDomain.DefineDynamicAssembly(_assemblyName, AssemblyBuilderAccess.RunAndCollect);
            _moduleBuilder = _assemblyBuilder.DefineDynamicModule(_assemblyName.Name, _assemblyName.Name + 
".dll");
        }

        
/// <summary>
        
/// Creates a dynamic type that lives in this dynamic namespace
        
/// </summary>
        
/// <param name="name">The name of the dynamic type</param>
        
/// <param name="properties">A collection of auto-properties the dynamic type will have</param>
        
/// <param name="fields">A collection of read/write fields the dynamic type will have</param>
        
/// <param name="baseType">Optional base type for this dynamic type to inherit from</param>
        
/// <param name="ifaces">Optional interfaces this dynamic type implements</param>
        
/// <param name="attrs">Optional interfaces this dynamic type implements</param>
        
/// <returns>The created dynamic type</returns>
        
public Type CreateClass(string name, Property[] properties, Property[] fields, Type baseType = nullType[] ifaces = nullAttribute[] attrs = null)
        {
            baseType = baseType ?? 
typeof(object);

            
// Define Type
            
var builder = _moduleBuilder.DefineType(name, TypeAttributes.Public, baseType, ifaces);

            
// ImplementInterfaces
            (ifaces ?? 
new Type[0]).ToList().ForEach(builder.AddInterfaceImplementation);

            
// Define Attributes
            (attrs ?? 
new Attribute[0]).ToList().ForEach(a => builder.SetCustomAttribute(GetAttributeBuilder(a)));

            
// Define Properties
            (properties ?? 
new Property[0]).ToList().ForEach(p => GetAutoPropertyBuilder(builder, p, ifaces));

            
// Define Fields
            (fields ?? 
new Property[0]).ToList().ForEach(f => builder.DefineField(f.Name, f.Type, FieldAttributes.Public));

            
// Forwarding all constructors
            
var baseCtors = baseType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            
foreach (var baseCtor in baseCtors)
            {
                
// Getting parameter information
                
var parms = baseCtor.GetParameters();
                
var parmTypes = parms.Select(p => p.ParameterType).ToArray();
                
var reqModifiers = parms.Select(p => p.GetRequiredCustomModifiers()).ToArray();
                
var optModifiers = parms.Select(p => p.GetOptionalCustomModifiers()).ToArray();
                
if (parms.Length > 0 && parms.Last().IsDefined(typeof(ParamArrayAttribute), false)) { continue; }

                
var ctor = builder.DefineConstructor(MethodAttributes.Public, baseCtor.CallingConvention, parmTypes, reqModifiers, optModifiers);

                
// Adding parameters
                
for (var i = 0; i < parms.Length; i++)
                {
                    
var parm = ctor.DefineParameter(i + 1, parms[i].Attributes, parms[i].Name);
                    
if (((int)parms[i].Attributes & (int)ParameterAttributes.HasDefault) != 0)
                    {
                        parm.SetConstant(parms[i].RawDefaultValue);
                    }
                    parms[i].DynamicAttributes()
                        .ToList()
                        .ForEach(a => parm.SetCustomAttribute(GetAttributeBuilder(a)));
                }

                
// Setting attributes
                baseCtor.DynamicAttributes()
                    .ToList()
                    .ForEach(a => ctor.SetCustomAttribute(GetAttributeBuilder(a)));

                
// Forwarding parameters to baseCtor
                
var il = ctor.GetILGenerator();
                il.Emit(
OpCodes.Nop);

                
// Loading args
                il.Emit(
OpCodes.Ldarg_0); // 'this'
                
for (var i = 1; i <= parms.Length; i++)
                    il.Emit(
OpCodes.Ldarg, i); // param i
                
// Calling baseCtor with loaded args
                il.Emit(
OpCodes.Call, baseCtor);
                il.Emit(
OpCodes.Ret);
            }

            _classes.Add(builder.CreateType()); 
// Generate Type and return
            
return _classes.Last();
        }

        
/// <summary>
        
/// Creates an dynamic enum type that lives in this dynamic namespace
        
/// </summary>
        
/// <typeparam name="TBase">The underlying value type for this enumeration</typeparam>
        
/// <param name="name">The name of this dynamic enum</param>
        
/// <param name="values">A collection of names/values this dynamic enum will have</param>
        
/// <returns>The created dynamic enum</returns>
        
public Type CreateEnum<TBase>(string name, IDictionary<string, TBase> values)
        {
            
var builder = _moduleBuilder.DefineEnum(name, TypeAttributes.Public, typeof(TBase));

            
// Create literals
            
foreach (var value in values)
            {
                builder.DefineLiteral(value.Key, value.Value);
            }

            _enums.Add(builder.CreateType());
            
return _enums.Last();
        }

        
/// <summary>
        
/// Creates an dynamic interface type that lives in this dynamic namespace
        
/// </summary>
        
/// <param name="name">The name of this dynamic interface</param>
        
/// <param name="properties">The property signatures for this dynamic interface</param>
        
/// <param name="methods">The method signatures for this dynamic interface</param>
        
/// <param name="ifaces">Optional interfaces this dynamic interface implements</param>
        
/// <returns>The dynamic interface</returns>
        
public Type CreateInterface(string name, Property[] properties, Method[] methods, Type[] ifaces = nullAttribute[] attrs = null)
        {
            
// Define Type
            
var builder = _moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract, null, ifaces);

            
// Define Attributes
            (attrs ?? 
new Attribute[0]).ToList().ForEach(a => builder.SetCustomAttribute(GetAttributeBuilder(a)));

            
// Define Properties
            
var propertyAttrs = MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Abstract | MethodAttributes.VtableLayoutMask | MethodAttributes.SpecialName;
            
foreach (var property in properties ?? new Property[0])
            {
                
var pbuild = GetPropertyBuilder(builder, property);
                pbuild.SetGetMethod(builder.DefineMethod(
"get_" + property.Name, propertyAttrs, property.Type, Type.EmptyTypes));
                pbuild.SetSetMethod(builder.DefineMethod(
"set_" + property.Name, propertyAttrs, typeof(void), new[] { property.Type }));
            }
            (properties ?? 
new Property[0]).ToList().ForEach(p => GetPropertyBuilder(builder, p));

            
// Define Methods
            
foreach (var method in methods ?? new Method[0])
            {
                
var methBuilder = builder.DefineMethod(
                    method.MethodName, 
MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual,
                    method.ReturnType, method.Parameters.Select(p => p.Type).ToArray());

                (method.Attributes ?? 
new Attribute[0]).ToList().ForEach(a => methBuilder.SetCustomAttribute(GetAttributeBuilder(a)));

                
for (var i = 0; i < method.Parameters.Length; i++)
                {
                    
var parm = method.Parameters[i];
                    
var parmBuilder = methBuilder.DefineParameter(i + 1, ParameterAttributes.None, parm.Name);
                    (parm.Attributes ?? 
new Attribute[0]).ToList().ForEach(a => parmBuilder.SetCustomAttribute(GetAttributeBuilder(a)));
                }
            }

            _interfaces.Add(builder.CreateType()); 
// Generate Type and return
            
return _interfaces.Last();
        }

        
private CustomAttributeBuilder GetAttributeBuilder(Attribute attribute)
        {
            
if (attribute.Properties.Count > 0 && attribute.Fields.Count > 0)
                
return new CustomAttributeBuilder(attribute.Constructor, attribute.Parameters,
                    attribute.Properties.Keys.ToArray(), attribute.Properties.Values.ToArray(),
                    attribute.Fields.Keys.ToArray(), attribute.Fields.Values.ToArray());
            
if (attribute.Properties.Count > 0)
                
return new CustomAttributeBuilder(attribute.Constructor, attribute.Parameters,
                    attribute.Properties.Keys.ToArray(), attribute.Properties.Values.ToArray());
            
if (attribute.Fields.Count > 0)
                
return new CustomAttributeBuilder(attribute.Constructor, attribute.Parameters,
                    attribute.Fields.Keys.ToArray(), attribute.Fields.Values.ToArray());
            
return new CustomAttributeBuilder(attribute.Constructor, attribute.Parameters);
        }

        
private PropertyBuilder GetAutoPropertyBuilder(TypeBuilder builder, Property property, Type[] ifaces)
        {
            
var field = builder.DefineField("_" + property.Name.ToLower() + "_field", property.Type, FieldAttributes.Private);
            
var pbuilder = GetWiredPropertyBuilder(builder, property, ifaces,
                get =>
                {
                    get.Emit(
OpCodes.Ldarg_0);
                    get.Emit(
OpCodes.Ldfld, field);
                    get.Emit(
OpCodes.Ret);
                },
                set =>
                {
                    set.Emit(
OpCodes.Ldarg_0);
                    set.Emit(
OpCodes.Ldarg_1);
                    set.Emit(
OpCodes.Stfld, field);
                    set.Emit(
OpCodes.Ret);
                });
            
return pbuilder;
        }

        
private PropertyBuilder GetWiredPropertyBuilder(TypeBuilder builder, Property property, Type[] ifaces, Action<ILGenerator> getter, Action<ILGenerator> setter)
        {
            
var prop = GetPropertyBuilder(builder, property);
            
var iprops = (ifaces ?? new Type[0]).Select(i => i.GetProperty(property.Name)).Where(p => p != null).ToList();

            
if (getter != null)
            {
                
var igets = iprops.Select(p => p.GetGetMethod()).Where(g => g != null).ToList();
                
var attr = igets.Count == 0
                    ? 
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig
                    : 
MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
                
var get = builder.DefineMethod("get_" + property.Name, attr, property.Type, Type.EmptyTypes);
                getter(get.GetILGenerator());
                prop.SetGetMethod(get);
                igets.ForEach(g => builder.DefineMethodOverride(get, g));
            }

            
if (setter != null)
            {
                
var isets = iprops.Select(p => p.GetSetMethod()).Where(s => s != null).ToList();
                
var attr = isets.Count == 0
                    ? 
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig
                    : 
MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
                
var set = builder.DefineMethod("set_" + property.Name, attr, typeof(void), new[] { property.Type });
                setter(set.GetILGenerator());
                prop.SetSetMethod(set);
                isets.ForEach(s => builder.DefineMethodOverride(set, s));
            }

            
return prop;
        }

        
private PropertyBuilder GetPropertyBuilder(TypeBuilder builder, Property property)
        {
            
var prop = builder.DefineProperty(property.Name, PropertyAttributes.None, property.Type, null);

            
// Defining Attributes
            (property.Attributes ?? 
new Attribute[0]).ToList().ForEach(a => prop.SetCustomAttribute(GetAttributeBuilder(a)));

            
return prop;
        }

        
/// <summary>
        
/// Releases internal references to the dynamic assembly <br />
        
/// WARNING: The memory consumed by a dynamic assembly and its types will not be released until all references to its types have been released from your code.
        
/// </summary>
        
public void Dispose()
        {
            _classes.Clear();
            _enums.Clear();
            _interfaces.Clear();
        }

        
public class Method
        {
            
public string MethodName { getprivate set; }
            
public Type ReturnType { getprivate set; }
            
public Parameter[] Parameters { getprivate set; }
            
public Attribute[] Attributes { getprivate set; }

            
public Method(string name, Type ret, Parameter[] parms, Attribute[] attr = null)
            {
                MethodName = name;
                ReturnType = ret;
                Parameters = parms;
                Attributes = attr;
            }
        }

        
public class Parameter
        {
            
public string Name { getprivate set; }
            
public Type Type { getprivate set; }
            
public Attribute[] Attributes { getprivate set; }

            
public Parameter(string name, Type type, Attribute[] attr = null)
            {
                Name = name;
                Type = type;
                Attributes = attr;
            }
        }

        
public class Property
        {
            
public string Name { getprivate set; }
            
public Type Type { getprivate set; }
            
public Attribute[] Attributes { getprivate set; }

            
public Property(string name, Type type, Attribute[] attr = null)
            {
                Name = name;
                Type = type;
                Attributes = attr;
            }
        }

        
public class Attribute
        {
            
public Type Type { getprivate set; }
            
public ConstructorInfo Constructor { getprivate set; }
            
public object[] Parameters { getprivate set; }
            
public Dictionary<PropertyInfoobject> Properties { getprivate set; }
            
public Dictionary<FieldInfoobject> Fields { getprivate set; }

            
public Attribute(Type type, ConstructorInfo ctor, object[] parms, IDictionary<PropertyInfoobject> props, IDictionary<FieldInfoobject> fields)
            {
                Type = type;
                Constructor = ctor;
                Parameters = parms;
                Properties = props.ToDictionary(d => d.Key, d => d.Value);
                Fields = fields.ToDictionary(d => d.Key, d => d.Value);
            }
        }
    }

    
public static class DynamicExtensions
    {
        
public static Namespace.Method[] DynamicMethods(this Type type)
        {
            
return type.GetMethods()
                .Select(m => 
new Namespace.Method(m.Name, m.ReturnType, m.GetParameters()
                    .Select(p => 
new Namespace.Parameter(p.Name, p.ParameterType))
                    .ToArray(), m.DynamicAttributes()))
                .ToArray();
        }

        
public static Namespace.Property[] DynamicProperties(this Type type)
        {
            
return type.GetProperties()
                .Select(p => 
new Namespace.Property(p.Name, p.PropertyType))
                .ToArray();
        }

        
public static Namespace.Attribute[] DynamicAttributes(this MemberInfo member)
        {
            
return member.GetCustomAttributesData().Select(ConvertAttribute).ToArray();
        }

        
public static Namespace.Attribute[] DynamicAttributes(this ParameterInfo param)
        {
            
return param.GetCustomAttributesData().Select(ConvertAttribute).ToArray();
        }

        
private static Namespace.Attribute ConvertAttribute(CustomAttributeData data)
        {
            
return new Namespace.Attribute(
                data.Constructor.DeclaringType, data.Constructor, data.ConstructorArguments.Select(p => (
object)p.Value).ToArray(),
                (data.NamedArguments ?? 
new CustomAttributeNamedArgument[0]).Where(p => p.MemberInfo is PropertyInfo).ToDictionary(p => (PropertyInfo)p.MemberInfo, p => p.TypedValue.Value),
                (data.NamedArguments ?? 
new CustomAttributeNamedArgument[0]).Where(p => !(p.MemberInfo is FieldInfo)).ToDictionary(p => (FieldInfo)p.MemberInfo, p => p.TypedValue.Value));
        }
    }
}
Basically you can just copy/paste this into a single .cs file in your project to try it out and see how it works. This is all System.Reflection.Emit with a small amount of IL in there. Most of this is just knowing how types are structured after being compiled and then just using the Emit builders to put it all together. What I've done here is packaged everything in a "hopefully" easy to understand API that allows you to create very simple class, interface, and enum types with auto-properties and what-not. If this helped you with some project somewhere it would be cool to hear about it. (www.markonthenet.com)