2
votes

I'm trying to create a reusable .NET Standard 2.0 library that uses Roslyn to dynamically compile code at runtime to an in-memory assembly. This dynamically created assembly contains classes that derive from a base class that is part of the library. I instantiate them via reflection in the apps that reference the library. The project structure looks like this:

Project structure

Suppose that I have the following type in my netstandard2.0 library:

namespace MyLibrary
{
    public abstract class BaseClass
    {
        public abstract int CalculateSomething();
    }
}

I then create the following unit test in a .NET Core 2.2 project:

namespace NetCore2_2.Tests
{
    public static class RoslynTests
    {
        [Fact]
        public static void CompileDynamicallyAndInvoke()
        {
            // Create syntax tree with simple class
            var syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;
using MyLibrary;

namespace Foo
{
    public sealed class Bar : BaseClass
    {
        public override int CalculateSomething()
        {
            return (int) Math.Sqrt(42);
        }
    }
}");
            // Create compilation, include syntax tree and reference to core lib
            var compilation = CSharpCompilation.Create(
                "MyDynamicAssembly.dll",
                new[] { syntaxTree },
                new[]
                {
                    MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof(BaseClass).Assembly.Location)
                },
                new CSharpCompilationOptions(
                    OutputKind.DynamicallyLinkedLibrary,
                    optimizationLevel: OptimizationLevel.Release)
            );

            // Compile it to a memory stream
            var memoryStream = new MemoryStream();
            var result = compilation.Emit(memoryStream);

            // If it was not successful, throw an exception to fail the test
            if (!result.Success)
            {
                var stringBuilder = new StringBuilder();
                foreach (var diagnostic in result.Diagnostics)
                {
                    stringBuilder.AppendLine(diagnostic.ToString());
                }

                throw new XunitException(stringBuilder.ToString());
            }

            // Otherwise load the assembly, instantiate the type via reflection and call CalculateSomething
            var dynamicallyCompiledAssembly = Assembly.Load(memoryStream.ToArray());
            var type = dynamicallyCompiledAssembly.GetType("Foo.Bar");
            var instance = (BaseClass) Activator.CreateInstance(type);
            int number = instance.CalculateSomething();
            Assert.Equal((int) Math.Sqrt(42), number);
        }
    }
}

In this test, I first parse a piece of C# code that derives from BaseClass in the netstandard2.0 library. This piece of code additionally references System.Math. I then create a C# compilation object that includes references to the core lib (of .NET Core 2.2) and my library. This compilation object emits the DLL to a memory stream. If compiling fails, the test will fail with an exception that contains all diagnostics.

This unit test does fail with the following error message:

(7,31): error CS0012: The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.

(11,26): error CS0012: The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.

I have the following questions:

  • Is this not working because the Roslyn NuGet package is referenced in a .NET Standard 2.0 project and thus always tries to compile to the netstandard2.0 Target Framework Moniker? I suspect that netstandard2.0 has a different definition of System.Object that forwards to the actual implementation of the target platform. And this forwarding definition is not referenced in my compilation unit.
  • Is there a way to change the target framework? I looked at CSharpCompilationOptions and EmitOptions, but couldn't find anything that let's me change the target framework.
  • Do I maybe need to use another Roslyn NuGet package such as Microsoft.Net.Compilers.Toolset? I try to avoid this because actually want to use the default compilers instead of the ones in the NuGet package.
1

1 Answers

1
votes
  • It's not working because your library, which contains BaseClass, targets .netstandard2.0 (it means that this library references netstandard.dll 2.0) and this assumes that your library, which references library with BaseClass, should have a reference to netstandard.dll 2.0 to correctrly resolve all corresponding types. So you should add reference on them (the netstandard.dll for .net47 or the similar .netstandard.dll for .netcore2.2). (By the way, when you reference .netstandard2.0 from .net47 library you probably should add a couple of additional libraries as reference from path_to_visual_studio\MSBuild\Microsoft\Microsoft.NET.Build.Extensions)
  • Roslyn Compilation doesn't know anything about target framework and it should not know anything about it. Compilation works with trees and references (and with some options and metadata of references of course), so you should manually append references that will be required in compilation. (By the way, if you have a csproj or a sln file you may use MsBuildWorkspace that allow to get a ready compilation from the project or solution file, in the most of cases)
  • I suggest to create Compilation by hand if you know or can find out all references that will be required in compilation, else try to use Microsoft.CodeAnalysis.Workspaces.MSBuild to analyze .csproj or .sln files and then retrieve Compilation from them. Microsoft.Net.Compilers.Toolset just gives to you possibility to compile your project by compilers not are installed on your system, but are contained in this package.