Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Apply XmlIncludeAttribute to TypeBuilder?

I am developing a library in C# that generates runtime types using System.Reflection.Emit.TypeBuilder class and i want to generate the following class hierarchy:

[XmlInclude(typeof(Derived))]
public class Base
{
}

public class Derived : Base
{
}

I use the TypeBuilder class in the following way:

class Program
{
    public static void Main(string[] args)
    {
        var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run);

        var moduleBuilder = assembly.DefineDynamicModule("Test");

        var baseTypeBuilder = moduleBuilder.DefineType("Base", TypeAttributes.Public, typeof(Object));

        var derivedTypeBuilder = moduleBuilder.DefineType("Derived", TypeAttributes.Public);

        derivedTypeBuilder.SetParent(baseTypeBuilder);

        baseTypeBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(XmlIncludeAttribute).GetConstructor(new[] { typeof(Type) }), new[] { derivedTypeBuilder }));

        var baseType = baseTypeBuilder.CreateType();

        var derivedType = derivedTypeBuilder.CreateType();

        var attribute = baseType.GetCustomAttribute<XmlIncludeAttribute>();
    }
}

The call:

var attribute = baseType.GetCustomAttribute<XmlIncludeAttribute>();

I receive the following error:

Could not load file or assembly 'Test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

Any ideas are well-appreciated: how can i apply a custom attribute on a TypeBuilder for base class that refers to a TypeBuilder for a derived class?

P.S: I'm using Visual Studio 2017 (v15.7.5) and a C# Class Library (.NET Framework project template) NOT .NET Core or .NET Standard

like image 759
Timothy Ghanem Avatar asked Aug 02 '18 08:08

Timothy Ghanem


3 Answers

Cannot provide much reason but a solution that will work without additional load/unload or whatever from and to disk:

Adding a separate field containing the actual Assembly, one can just subscribe to AppDomain.CurrentDomain.AssemblyResolve and check if the dynamic assembly was requested.

If it was, you just need to return your field and it works perfectly fine.

Example:

class Program
{
    static Assembly ass;
    public static void Main(string[] args)
    {
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.Run);

        var moduleBuilder = assembly.DefineDynamicModule("Test");

        var baseTypeBuilder = moduleBuilder.DefineType("Base", TypeAttributes.Public, typeof(Object));

        var derivedTypeBuilder = moduleBuilder.DefineType("Derived", TypeAttributes.Public);

        derivedTypeBuilder.SetParent(baseTypeBuilder);

        baseTypeBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(XmlIncludeAttribute).GetConstructor(new[] { typeof(Type) }), new[] { derivedTypeBuilder }));

        var baseType = baseTypeBuilder.CreateType();

        var derivedType = derivedTypeBuilder.CreateType();
        ass = baseType.Assembly;

        var attribute = baseType.GetCustomAttribute<XmlIncludeAttribute>();

        Console.WriteLine(attribute.Type.FullName);
    }

    private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        return ass;
    }
}
like image 163
X39 Avatar answered Nov 12 '22 20:11

X39


I've reproduced your exception. It's looks like the .NET Framework want to read a module from disk.

So simplest workaround would be to save your assembly to the disk:

var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
  new AssemblyName("Test"), 
  AssemblyBuilderAccess.RunAndSave); // allow run & save

var moduleBuilder = assembly.DefineDynamicModule("Test",
  "Test.dll"); // specify a file name where module will be stored

...

var baseType = baseTypeBuilder.CreateType();
var derivedType = derivedTypeBuilder.CreateType();

assembly.Save("Test.dll");

Now I was able to get attribute without exception:

var attribute = baseType.GetCustomAttribute<XmlIncludeAttribute>();
like image 29
Aleks Andreev Avatar answered Nov 12 '22 18:11

Aleks Andreev


Well, I can tell you why the fix @X39 posted works. Stepping through the forest of framework code (perhaps fallbacks for different cultures) for the baseType.GetCustomAttribute(); call, when finally reaching appdomain.cs I see:

enter image description here

At this point, _AssemblyResolve is null (which is the private backing field for AppDomain.CurrentDomain.AssemblyResolve) and after stepping through to return null;, the code crashes with the error you posted.

More info: https://learn.microsoft.com/en-us/dotnet/api/system.appdomain.assemblyresolve?view=netframework-4.7.2

@X39 feel free to merge my answer and let me know so that I can remove mine, I did not want to edit your answer directly.

like image 1
Alexandru Clonțea Avatar answered Nov 12 '22 20:11

Alexandru Clonțea