Node.JS
Node.JS

Reputation: 1578

Using AssemblyBuilder to build a complex type

I have been working on small library to build very simple types dynamically using AssemblyBuilder. I can build a type, add properties into it. But the problem I am facing is adding a property given a dynamically generated type that was also generated using this simple library. In other works, creating a complex type. I tried adding namespace to the type, but I think the source of my problem is not importing the nested type while creating the parent type.

How I create the type and nested type:

var token = "TestNameSpace";
var nestedType = Builders.CustomTypeBuilder.New(@namespace: token).AddProperty<string>("NestedProp").Compile();

var propertyName = "NestedProp";
var obj = Builders.CustomTypeBuilder.NewExtend<DummyClass>(@namespace: token)
    .AddProperty(propertyName, nestedType)
    .Instantiate<DummyClass>();

The nested type (or NestedProp) is kind of undefined ("Unresolved ...").

enter image description here

The main logic behind the library:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using CustomTypeBuilder.Extensions;
using static CustomTypeBuilder.Utilities.TypeUtility;

namespace CustomTypeBuilder
{
    /// <summary>
    /// Creates a new type dynamically
    /// </summary>
    public class CustomTypeGenerator
    {
        private readonly TypeBuilder _typeBuilder;

        private readonly Dictionary<string, Type> _properties;

        /// <summary>
        /// Initialize custom type builder
        /// </summary>
        /// <param name="name"></param>
        /// <param name="parentType"></param>
        /// <param name="namespace"></param>
        public CustomTypeGenerator(string name = null, Type parentType = null, string @namespace = null)
        {
            var assemblyName = RandomSafeString("DynamicAseembly");
            var typeSignature = name ?? RandomSafeString("DynamicType");

            // add namespace
            if (@namespace != name)
            {
                typeSignature = $"{@namespace}.{typeSignature}";
            }

            var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(RandomSafeString("Module"));
            _typeBuilder = moduleBuilder.DefineType(typeSignature,
                TypeAttributes.Public |
                TypeAttributes.Class |
                TypeAttributes.AutoClass |
                TypeAttributes.AnsiClass |
                TypeAttributes.BeforeFieldInit |
                TypeAttributes.AutoLayout,
                parentType);

            _typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

            _properties = new Dictionary<string, Type>();
        }

        /// <summary>
        /// Add attribute to the class
        /// </summary>
        /// <param name="attribute"></param>
        public void AddAttribute(Attribute attribute)
        {
            _typeBuilder.SetCustomAttribute(attribute.BuildCustomAttribute());
        }

        /// <summary>
        /// Compile the type builder to a type
        /// </summary>
        /// <returns></returns>
        public Type CompileResultType()
        {            
            return _typeBuilder.CreateType();
        }

        /// <summary>
        /// Add interfaces to a type
        /// </summary>
        /// <param name="type"></param>
        public void AddInterface(Type type)
        {
            if (!type.IsInterface)
            {
                throw new ArgumentException("Type was expected to be an interface");
            }

            _typeBuilder.AddInterfaceImplementation(type);

            // add types in interface
            type.GetProperties().ForEach(x => AddProperty(x.Name, x.PropertyType));
        }

        public void ExtendType(Type type)
        {
            _typeBuilder.SetParent(type);
        }

        public void AddProperty(string propertyName, Type propertyType)
        {
            if (!IsValidName(propertyName)) throw new ArgumentException("Property name does not follow to C# type system");

            if (_properties.Keys.Any(x => x == propertyName)) throw new ArgumentException("Duplicate property name");

            // add property to dictionary
            _properties.Add(propertyName, propertyType);

            var fieldBuilder = _typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            var propertyBuilder = _typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            var getPropMthdBldr = _typeBuilder.DefineMethod("get_" + propertyName,
                MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType,
                Type.EmptyTypes);
            var getIl = getPropMthdBldr.GetILGenerator();

            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getIl.Emit(OpCodes.Ret);

            var setPropMthdBldr = _typeBuilder.DefineMethod("set_" + propertyName,
                    MethodAttributes.Public |
                    MethodAttributes.SpecialName |
                    MethodAttributes.HideBySig,
                    null, new[] {propertyType});

            var setIl = setPropMthdBldr.GetILGenerator();
            var modifyProperty = setIl.DefineLabel();
            var exitSet = setIl.DefineLabel();

            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Stfld, fieldBuilder);

            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }
    }
}

Any help would be appreciated. Thank you.

Upvotes: 0

Views: 1601

Answers (1)

Joshua Webb
Joshua Webb

Reputation: 646

While not exactly a solution to your problem, I hope some of this information helps.

The debugger from Visual Studio 2017 and .NET Core 2.0 seems to display the type/object okay.

Visual Studio Example 1

The nested type can be set and displayed correctly too.

Visual Studio Example 2

It also appears to work correctly in VS Code

VSCode Example

Curiously, the Rider debugger and .NET Core 2.0 appears to be able to identify the nested property type correctly when we instantiate and assign an object of nestedType before constructing the other object type that uses it, but it doesn't work if we merely instantiate it.

e.g.

var token = "TestNameSpace";
var nestedType = Builders.CustomTypeBuilder.New(@namespace: token).AddProperty<string>("NestedProp").Compile();
var propertyName = "NewProperty";

var someInstance = Activator.CreateInstance(nestedType);

var obj = Builders.CustomTypeBuilder.NewExtend<DummyClass>(@namespace: token)
    .AddProperty(propertyName, nestedType)
    .Instantiate<DummyClass>();

var nestedValue = Activator.CreateInstance(nestedType);

nestedType.GetProperty("NestedProp").SetValue(nestedValue, "NestedValue");
obj.GetType().GetProperty(propertyName).SetValue(obj, nestedValue);
Debugger.Break();

Rider example 1

vs.

var token = "TestNameSpace";
var nestedType = Builders.CustomTypeBuilder.New(@namespace: token).AddProperty<string>("NestedProp").Compile();
var propertyName = "NewProperty";

// Instantiated, but not assigned to a local variable
Console.WriteLine(Activator.CreateInstance(nestedType));

var obj = Builders.CustomTypeBuilder.NewExtend<DummyClass>(@namespace: token)
    .AddProperty(propertyName, nestedType)
    .Instantiate<DummyClass>();

var nestedValue = Activator.CreateInstance(nestedType);

nestedType.GetProperty("NestedProp").SetValue(nestedValue, "NestedValue");
obj.GetType().GetProperty(propertyName).SetValue(obj, nestedValue);
Debugger.Break();

Rider example 2

If however, we create the nested type, instantiate it and assign it to a variable in a separate scope first, then it seems to depend on whether the debugger paused after the assignment or not.

e.g.

static Type CreateNestedType()
{
    var nestedType = Builders.CustomTypeBuilder.New(@namespace: "TestNameSpace")
                             .AddProperty<string>("NestedProp")
                             .Compile();

    var x = Activator.CreateInstance(nestedType);
    Console.WriteLine(x.GetType());
    // Toggling this line changes the behaviour of the debugger at the *next* breakpoint.
    //Debugger.Break();
    return nestedType;
}

static void Main(string[] args)
{
    var nestedType = CreateNestedType();
    var propertyName = "NewProperty";

    var obj = Builders.CustomTypeBuilder.NewExtend<DummyClass>(@namespace: "TestNameSpace")
        .AddProperty(propertyName, nestedType)
        .Instantiate<DummyClass>();

    var nestedValue = Activator.CreateInstance(nestedType);

    nestedType.GetProperty("NestedProp").SetValue(nestedValue, "NestedValue");
    obj.GetType().GetProperty(propertyName).SetValue(obj, nestedValue);
    Debugger.Break();
}

When actually using the objects within the program, they seem to behave correctly despite not appearing correctly in the debugger.

When I compiled and saved the dynamic assemblies using .NET Framework instead of .NET Core, they both pass PEVerify.

Rider seems to handle all of the above cases when we define both of the types in the same dynamic assembly/module instead of using a new dynamic assembly for each type.

Based on all of this it seems plausible that this is an issue/bug/missing feature/shortcoming (whatever you want to call it) in Rider's debugger, you may want to raise it directly with JetBrains to see if they can shed any more light on the situation.

Upvotes: 2

Related Questions