Search code examples
c#inheritancereflectioncollectionstypebuilder

Reflection Emit Derived by ObservableCollection


I want to create dynamic type with refelction emit like:

public class ObservableTestColleciton<T> : ObservableCollection<T>
{
    public T Parent { get; set; }
    public ObservableTestColleciton(T parent)
    {
        Parent = parent;
    }
    public ObservableTestColleciton(T parent, IEnumerable<T> source):base(source)
    {
        Parent = parent;
    }
}

The code I could not complete is this like:

 AppDomain myDomain = AppDomain.CurrentDomain;
    AssemblyName myAsmName = new AssemblyName("AAB");
    AssemblyBuilder myAssembly =      myDomain.DefineDynamicAssembly(myAsmName,AssemblyBuilderAccess.Save);
    ModuleBuilder myModule = myAssembly.DefineDynamicModule(myAsmName.Name,myAsmName.Name + ".dll");
    TypeBuilder myType = myModule.DefineType("ObservableTestCollection", TypeAttributes.Class | TypeAttributes.Public);

    string[] typeParamNames = { "T" };
    GenericTypeParameterBuilder[] typeParams = myType.DefineGenericParameters(typeParamNames);

    Type observableOf = typeof(ObservableCollection<>);
    Type genOb = observableOf.MakeGenericType(typeParams[0]);          
    FieldBuilder myField = myType.DefineField("Parent", typeParams[0], FieldAttributes.Public);
    ConstructorBuilder constructor = myType.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, Type.EmptyTypes);         

    var type = myType.CreateType();
    var obj = Activator.CreateInstance(type);
    myAssembly.Save("AAB.dll");

Your help would be much appreciated!!


Solution

  • Your solution has several problems:

    • AssemblyBuilderAccess should be RunAndSave to allow type instance create objects in run time.
    • You need to specify body for constructor.
    • In constructor body you should call base type (ObservableCollection) constructor.
    • In constructor body you should set field value from constructor parameters.

    My solution for this problem with both constructors is something like this:

            const string typeName = "ObservableTestCollection";
            const string fieldName = "Parent";
            const string assemblyName = "TestAssembly";
            const string assemblyFileName = assemblyName + ".dll";
    
            var domain = AppDomain.CurrentDomain;
            var assemblyBuilder = domain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndSave);
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName, assemblyFileName);
    
            var baseType = typeof(ObservableCollection<>);
            var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, baseType);
            var genericParameters = typeBuilder.DefineGenericParameters("T");
            var genericParameter = genericParameters.First();
    
            var fieldBuilder = typeBuilder.DefineField(fieldName, genericParameter, FieldAttributes.Public);
    
            //First constructor ObservableTestColleciton(T parent)
            var ctorParameters = new Type[] { genericParameter };
            var baseCtor = baseType.GetConstructor(Type.EmptyTypes);
            var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, ctorParameters);
            var generator = ctorBuilder.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0); // load this
            generator.Emit(OpCodes.Call, baseCtor); //call base constructor
            generator.Emit(OpCodes.Ldarg_0); // load this
            generator.Emit(OpCodes.Ldarg_1); // load argument value
            generator.Emit(OpCodes.Stfld, fieldBuilder); // store into Parent field
            generator.Emit(OpCodes.Ret); //return
    
            //Second constructor ObservableTestColleciton(T parent, IEnumerable<T> source):base(source)
            var baseCtorParam = typeof(IEnumerable<>).MakeGenericType(genericParameter);
            ctorParameters = new [] { genericParameter, baseCtorParam };
            baseCtor = baseType.GetConstructors()
                               .First(c => c.GetParameters().FirstOrDefault()?.ParameterType?.GetGenericTypeDefinition() == typeof(IEnumerable<>));
    
            ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, ctorParameters);
            generator = ctorBuilder.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0); // load this
            generator.Emit(OpCodes.Ldarg_2); // load second argument value
            generator.Emit(OpCodes.Call, baseCtor); //call base constructor
            generator.Emit(OpCodes.Ldarg_0); // load this
            generator.Emit(OpCodes.Ldarg_1); // load first argument value
            generator.Emit(OpCodes.Stfld, fieldBuilder); // store into Parent field
            generator.Emit(OpCodes.Ret); //return
    
            var genericType = typeBuilder.CreateType();
            var type = genericType.MakeGenericType(typeof(string));
            var fieldInfo = type.GetField(fieldName);
            var obj1 = Activator.CreateInstance(type, "Parent1");
            Console.WriteLine("Ctor1 field value :" + fieldInfo.GetValue(obj1)); //check that field value was set
            var obj2 = Activator.CreateInstance(type, "Parent2", new List<string>());
            Console.WriteLine("Ctor1 field value :" + fieldInfo.GetValue(obj2));
            assemblyBuilder.Save(assemblyFileName);