2
votes

I'm trying to compile an assembly using CSharpCompilation that has a simple class in it, that I can then reference in another program that is also compiled through CSharpCompilation. I have this code:

namespace CompilationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            HashSet<Assembly> referencedAssemblies = new HashSet<Assembly>()
            {
                typeof(object).Assembly,
                Assembly.Load(new AssemblyName("Microsoft.CSharp")), 
                Assembly.Load(new AssemblyName("netstandard")),
                Assembly.Load(new AssemblyName("System.Runtime")),
                Assembly.Load(new AssemblyName("System.Linq")),
                Assembly.Load(new AssemblyName("System.Linq.Expressions"))
            };
            
            string greetingClass = @"
namespace TestLibraryAssembly
{
    public static class Greeting
    {
        public static string GetGreeting(string name)
        {
            return ""Hello, "" + name + ""!"";
        }
    }
}
";
            
            CSharpCompilation compilation1 = CSharpCompilation.Create(
                "TestLibraryAssembly",
                new []
                {
                    CSharpSyntaxTree.ParseText(greetingClass)
                },
                referencedAssemblies.Select(assembly => MetadataReference.CreateFromFile(assembly.Location)).ToList(),
                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            );
            MemoryStream memoryStream1 = new MemoryStream();
            EmitResult emitResult1 = compilation1.Emit(memoryStream1);
            memoryStream1.Position = 0;
            MetadataReference testLibraryReference = MetadataReference.CreateFromStream(memoryStream1);

            string programCode = @"
using TestLibraryAssembly;

namespace TestProgram
{
    public class Program
    {
        public void Main()
        {
            string greeting = Greeting.GetGreeting(""Name"");
        }
    }
}
";
            
            CSharpCompilation compilation2 = CSharpCompilation.Create(
                "TestProgram",
                new []
                {
                    CSharpSyntaxTree.ParseText(programCode)
                },
                referencedAssemblies
                    .Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
                    .Concat(new List<MetadataReference> { testLibraryReference }).ToList(),
                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            );
            MemoryStream memoryStream2 = new MemoryStream();
            EmitResult emitResult2 = compilation2.Emit(memoryStream2);
            memoryStream2.Position = 0;
            
            Assembly programAssembly = Assembly.Load(memoryStream2.ToArray());
            Type programType = programAssembly.GetType("TestProgram.Program");
            MethodInfo method = programType.GetMethod("Main");
            object instance = Activator.CreateInstance(programType);
            method.Invoke(instance, null);
        }
    }
}

However, when I run this, I get this error:

Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.

---> System.IO.FileNotFoundException: Could not load file or assembly 'TestLibraryAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.

File name: 'TestLibraryAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'

How can I fix this error?

2

2 Answers

2
votes

The problem in your example is how the Activator (Activator.CreateInstance(programType);) is trying to resolve the dependencies. It will look on the disk for files.

One way to work around this is to save the files on disk and later use Activator.CreateInstanceFrom to create an instance.

You can find here a snippet which is doing that:

using System;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Playground
{
    public static class Program
    {
        private const string firstClass =
@"
namespace A
{
    public class Foo
    {
        public int Bar() => 21;
    }
}";

        private const string secondClass =
@"using A;

namespace B
{
    public class Test
    {
        public int GetValue() => new Foo().Bar();
    }
}
";

        public static void Main()
        {
            var firstAssemblyFileName = Path.Combine(Path.GetTempPath(), "A.dll");
            var secondAssemblyFileName = Path.Combine(Path.GetTempPath(), "B.dll");

            var compilation = CreateCompilation(CSharpSyntaxTree.ParseText(firstClass), "A");
            var secondCompilation = CreateCompilation(CSharpSyntaxTree.ParseText(secondClass), "B")
                .AddReferences(compilation.ToMetadataReference());

            compilation.Emit(firstAssemblyFileName);
            secondCompilation.Emit(secondAssemblyFileName);

            dynamic testType = Activator.CreateInstanceFrom(secondAssemblyFileName, "B.Test").Unwrap();
            var value = testType.GetValue();
        }

        private static CSharpCompilation CreateCompilation(SyntaxTree tree, string name) =>
            CSharpCompilation
                .Create(name, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
                .AddSyntaxTrees(tree);
    }
}
0
votes

Since you are using netstandard, there is no reference to System.IO.

Add this line to referencedAssemblies HashSet declaration section:

Assembly.Load("System.IO.FileSystem")

Edited:

Or more safe to use:

typeof(File).Assembly