Reputation: 5728
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:
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:
System.Object
that forwards to the actual implementation of the target platform. And this forwarding definition is not referenced in my compilation unit.CSharpCompilationOptions
and EmitOptions
, but couldn't find anything that let's me change the target framework.Upvotes: 3
Views: 1567
Reputation: 2936
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
)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)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.Upvotes: 1