简体   繁体   中英

In C#, how do you reference types from one in-memory assembly inside another?

The example program below compiles two in-memory assemblies. The first compilation works fine. The second one fails because it needs access to a class from the first assembly and the type isn't available.

Specifically: The ReferencedAssemblies member of the CompilerParameters class is a string collection and it is used to load the manifests of the assemblies to obtain their types. It appears the C# compiler gets types strictly from the manifest rather than by using reflection (possibly for performance reasons.) In any case, when an assembly is constructed in memory there is no file and no manifest so the second assembly build fails with an error like this:

COMPILER ERROR: Metadata file 'ax5lw0tl, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' could not be found

Adding an AssemblyResolver event handler doesn't work. I tried this and it looks like it isn't ever called. From what I can tell (and I'm a novice with .Net so bear with me) the compiler only cares about the manifest; it's not actually trying to load the assembly at this time, so AssemblyResolver isn't in the picture.

I could, if desperate, construct my assemblies on disk which would solve the immediate problem to have a physical dll and manifest to read. I would much rather not do this as it leads to having to manage what will become a very large collection of temporary assemblies on disk.

I'm optimistic .Net can do this and, being a novice, I'm simply missing it.

(I hope the spacing comes out ok on the code sample. It seems to render properly in the preview window for a few moments but once the syntax highlighter is done it rerenders and the spacing is incorrect although it remains readable.)

using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.CSharp;  

namespace AsmCompileTest
  {
  class Program
    {
    static Assembly Compile( string code, Assembly referencedAssembly )
      {
      CompilerParameters cp = new CompilerParameters();
      cp.GenerateExecutable = false;
      cp.GenerateInMemory = true;

      if( null != referencedAssembly )
        {
        cp.ReferencedAssemblies.Add( referencedAssembly.FullName );
        }

      CodeDomProvider provider = new CSharpCodeProvider( new Dictionary<string,string> { { "CompilerVersion", "v3.5" } } );

      CompilerResults compilerResults = provider.CompileAssemblyFromSource( cp, code );

      if( compilerResults.Errors.HasErrors )
        {
        foreach( CompilerError error in compilerResults.Errors )
          {
          Console.WriteLine( "COMPILER ERROR: " + error.ErrorText );
          }
        }

      return compilerResults.CompiledAssembly;
      }


    static string Code1 = "using System;" +
                          "public class HelloClass" +
                          "  {" +
                          "  public HelloClass() { Console.WriteLine( \"Hello, World!\" ); }" +
                          "  }";


    static string Code2 = "using System;" +
                          "public class TestClass" +
                          "  {" +
                          "  public TestClass() { new HelloClass(); }" +
                          "  }";

    static void Main()
      {
      Assembly asm1 = Compile( Code1, null );
      Console.WriteLine( "Compiled: " + asm1.FullName );  

      asm1.GetType( "HelloClass" ).InvokeMember( String.Empty, BindingFlags.CreateInstance, null, null, null );  

      Assembly asm2 = Compile( Code2, asm1 );
      Console.WriteLine( "Compiled: " + asm2.FullName );  

      asm2.GetType( "TestClass" ).InvokeMember( String.Empty, BindingFlags.CreateInstance, null, null, null );
      }
    }
  }

Based on documentation found on MSDN and on the code in reflector that I looked at (for the compiler classes) it is not possible to do what you want. The reason is that underneath, the code compiler classes that you are using shell out to the actual compiler.

Also, the code compiler classes are actually generating the temporary files underneath, and based on the code I looked at in reflector, they are not cleaning up the files. So based on that, I would say just generate the file on the disk in a temporary location, and then add reference to it.

Define interfaces in a normal assembly and have classes in each generated assembly implement those interfaces. The generated assemblies will need a reference to the one containing the interfaces, not each other.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM