Roslyn 编译 - 如何引用 .NET Standard 2.0 class 库
Roslyn compilation - how to reference a .NET Standard 2.0 class library
我创建了一个控制台应用程序项目(针对 .NET Core 3.0)和一个 class 库(针对 .NET Standard 2.0)。控制台应用程序尝试使用 Roslyn 编译器编译一些引用先前创建的 class 库的 C# 代码。不过我遇到了一些重大问题。
这是控制台应用程序的代码(请注意,其中大部分是来自 https://github.com/joelmartinez/dotnet-core-roslyn-sample/blob/master/Program.cs 的示例代码):
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; //nuget Microsoft.CodeAnalysis.CSharp
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
//This is a class library I made in a separate project in the solution and added as a reference to the console application project.
//The important bit for the reproduction of the issue is that ClassLibrary1 targets .NET Standard 2.0.
using ClassLibary1;
namespace RoslynIssueRepro
{
class Program
{
static void Main(string[] args)
{
string codeToCompile =
@"
using ClassLibary1;
using System;
namespace RoslynCompileSample
{
public class Writer
{
public void Execute()
{
//1. this next line of code introduces the issue during Roslyn compilation (comment it out and everything runs fine).
//It causes the code to reference a .NET Standard 2.0 class library (and this console app targets .NET Core 3.0).
//Note: If the referenced class library targets .NET Core 3.0, everything works fine.
//The error looks like this:
// 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'.
Console.WriteLine(Class1.DoStuff());
Console.WriteLine(""Freshly compiled code execution done!"");
}
}
}";
var refPaths = new[] {
typeof(System.Object).GetTypeInfo().Assembly.Location,
typeof(Console).GetTypeInfo().Assembly.Location,
Path.Combine(Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll"),
typeof(Class1).GetTypeInfo().Assembly.Location,
//2. So adding a reference to netstandard.dll to alleviate the issue does not work.
//Instead it causes even more compilation errors of this form:
// CS0518: Predefined type 'System.Object' is not defined or imported
// CS0433: The type 'Console' exists in both 'System.Console, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' and 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
//Go ahead and try it by uncommenting the line below:
//Environment.ExpandEnvironmentVariables(@"C:\Users\%USERNAME%\.nuget\packages\netstandard.library.0.0\build\netstandard2.0\ref\netstandard.dll")
};
RoslynCompileAndExecute(codeToCompile, refPaths);
}
#region example code from https://github.com/joelmartinez/dotnet-core-roslyn-sample/blob/master/Program.cs
private static void RoslynCompileAndExecute(string codeToCompile, string[] refPaths)
{
Write("Let's compile!");
Write("Parsing the code into the SyntaxTree");
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(codeToCompile);
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = refPaths.Select(r => MetadataReference.CreateFromFile(r)).ToArray();
Write("Adding the following references");
foreach (var r in refPaths)
Write(r);
Write("Compiling ...");
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
Write("Compilation failed!");
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("\t{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
else
{
Write("Compilation successful! Now instantiating and executing the code ...");
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var type = assembly.GetType("RoslynCompileSample.Writer");
var instance = assembly.CreateInstance("RoslynCompileSample.Writer");
var meth = type.GetMember("Execute").First() as MethodInfo;
meth.Invoke(instance, null);
}
}
}
static Action<string> Write = Console.WriteLine;
#endregion
}
}
ClassLibrary1 的代码就是这样:
namespace ClassLibary1
{
public static class Class1
{
public static string DoStuff()
{
return "asdfjkl";
}
}
}
我在代码中用 //1 和 //2 注释了两个地方。第一个是引入第一个问题并导致编译失败的行。第二点(目前已注释掉)是尝试通过添加对 netstandard.dll 文件的引用来解决第一个问题(抱歉,如果路径不可移植,就在我碰巧在我的机器上找到它的地方) , 但它并没有解决任何问题,只是引入了更多神秘的错误。
对于使此代码正常工作我应该采取的方法有什么想法吗?
第一个错误发生是因为您引用的库目标 netstandard
并且引用该库的控制台应用程序编译必须引用 netstandard.dll
才能正确解析所有对应的类型。所以你应该添加对 nestandard.dll
的引用,但它不是全部,这里你会得到第二个错误。
当您尝试直接或通过传递引用 netsandard
时,您必须提供目标平台对应的 nestandard.dll
。而这个 netstandard
将有一个巨大的转发类型到当前目标平台上的类型。如果你看@"C:\Users\%USERNAME%\.nuget\packages\netstandard.library.0.0\build\netstandard2.0\ref\netstandard.dll"
你会发现这个netstandard.dll
不包含forwards而是直接包含所有类型当然它包含System.Console
。 (我认为,它直接包含所有类型,因为它来自不依赖于任何目标平台的 nuget 包,但真的不确定)。当您尝试通过 typeof(Console).GetTypeInfo().Assembly.Location
添加它和 System.Console.dll
时,您实际上会在编译中得到两个 System.Console
。
因此,要解决这个不明确的问题,您可以添加 netstandard
不是来自此 nuget 包,而是来自您当前的目标平台,该平台具有所有需要的转发。例如,对于 .netcore30
,您可以使用 path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref.0.0\ref\netcoreapp3.0\
中的 netstandard
(注意上面的程序集和来自 nuget 包的程序集仅供参考,它们不包含真正的逻辑)。您也可以尝试删除 System.Console.dll
上的引用并保留 @"C:\Users\%USERNAME%\.nuget\packages\netstandard.library.0.0\build\netstandard2.0\ref\netstandard.dll"
上的引用
var dd = typeof(Enumerable).GetTypeInfo().Assembly.Location;
var coreDir = Directory.GetParent(dd);
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "netstandard.dll")
由于您正在使用 typeof(X).Assembly
定位所有其他引用的程序集,这将隐式地 return 将程序集加载到您的应用程序域中。我建议以同样的方式找到匹配的网络标准。但是,由于 netstandard 没有直接定义任何类型,我发现最好的方法是按名称搜索它;
AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "netstandard")
我创建了一个控制台应用程序项目(针对 .NET Core 3.0)和一个 class 库(针对 .NET Standard 2.0)。控制台应用程序尝试使用 Roslyn 编译器编译一些引用先前创建的 class 库的 C# 代码。不过我遇到了一些重大问题。
这是控制台应用程序的代码(请注意,其中大部分是来自 https://github.com/joelmartinez/dotnet-core-roslyn-sample/blob/master/Program.cs 的示例代码):
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; //nuget Microsoft.CodeAnalysis.CSharp
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
//This is a class library I made in a separate project in the solution and added as a reference to the console application project.
//The important bit for the reproduction of the issue is that ClassLibrary1 targets .NET Standard 2.0.
using ClassLibary1;
namespace RoslynIssueRepro
{
class Program
{
static void Main(string[] args)
{
string codeToCompile =
@"
using ClassLibary1;
using System;
namespace RoslynCompileSample
{
public class Writer
{
public void Execute()
{
//1. this next line of code introduces the issue during Roslyn compilation (comment it out and everything runs fine).
//It causes the code to reference a .NET Standard 2.0 class library (and this console app targets .NET Core 3.0).
//Note: If the referenced class library targets .NET Core 3.0, everything works fine.
//The error looks like this:
// 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'.
Console.WriteLine(Class1.DoStuff());
Console.WriteLine(""Freshly compiled code execution done!"");
}
}
}";
var refPaths = new[] {
typeof(System.Object).GetTypeInfo().Assembly.Location,
typeof(Console).GetTypeInfo().Assembly.Location,
Path.Combine(Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll"),
typeof(Class1).GetTypeInfo().Assembly.Location,
//2. So adding a reference to netstandard.dll to alleviate the issue does not work.
//Instead it causes even more compilation errors of this form:
// CS0518: Predefined type 'System.Object' is not defined or imported
// CS0433: The type 'Console' exists in both 'System.Console, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' and 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
//Go ahead and try it by uncommenting the line below:
//Environment.ExpandEnvironmentVariables(@"C:\Users\%USERNAME%\.nuget\packages\netstandard.library.0.0\build\netstandard2.0\ref\netstandard.dll")
};
RoslynCompileAndExecute(codeToCompile, refPaths);
}
#region example code from https://github.com/joelmartinez/dotnet-core-roslyn-sample/blob/master/Program.cs
private static void RoslynCompileAndExecute(string codeToCompile, string[] refPaths)
{
Write("Let's compile!");
Write("Parsing the code into the SyntaxTree");
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(codeToCompile);
string assemblyName = Path.GetRandomFileName();
MetadataReference[] references = refPaths.Select(r => MetadataReference.CreateFromFile(r)).ToArray();
Write("Adding the following references");
foreach (var r in refPaths)
Write(r);
Write("Compiling ...");
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
Write("Compilation failed!");
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine("\t{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
else
{
Write("Compilation successful! Now instantiating and executing the code ...");
ms.Seek(0, SeekOrigin.Begin);
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var type = assembly.GetType("RoslynCompileSample.Writer");
var instance = assembly.CreateInstance("RoslynCompileSample.Writer");
var meth = type.GetMember("Execute").First() as MethodInfo;
meth.Invoke(instance, null);
}
}
}
static Action<string> Write = Console.WriteLine;
#endregion
}
}
ClassLibrary1 的代码就是这样:
namespace ClassLibary1
{
public static class Class1
{
public static string DoStuff()
{
return "asdfjkl";
}
}
}
我在代码中用 //1 和 //2 注释了两个地方。第一个是引入第一个问题并导致编译失败的行。第二点(目前已注释掉)是尝试通过添加对 netstandard.dll 文件的引用来解决第一个问题(抱歉,如果路径不可移植,就在我碰巧在我的机器上找到它的地方) , 但它并没有解决任何问题,只是引入了更多神秘的错误。
对于使此代码正常工作我应该采取的方法有什么想法吗?
第一个错误发生是因为您引用的库目标 netstandard
并且引用该库的控制台应用程序编译必须引用 netstandard.dll
才能正确解析所有对应的类型。所以你应该添加对 nestandard.dll
的引用,但它不是全部,这里你会得到第二个错误。
当您尝试直接或通过传递引用 netsandard
时,您必须提供目标平台对应的 nestandard.dll
。而这个 netstandard
将有一个巨大的转发类型到当前目标平台上的类型。如果你看@"C:\Users\%USERNAME%\.nuget\packages\netstandard.library.0.0\build\netstandard2.0\ref\netstandard.dll"
你会发现这个netstandard.dll
不包含forwards而是直接包含所有类型当然它包含System.Console
。 (我认为,它直接包含所有类型,因为它来自不依赖于任何目标平台的 nuget 包,但真的不确定)。当您尝试通过 typeof(Console).GetTypeInfo().Assembly.Location
添加它和 System.Console.dll
时,您实际上会在编译中得到两个 System.Console
。
因此,要解决这个不明确的问题,您可以添加 netstandard
不是来自此 nuget 包,而是来自您当前的目标平台,该平台具有所有需要的转发。例如,对于 .netcore30
,您可以使用 path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref.0.0\ref\netcoreapp3.0\
中的 netstandard
(注意上面的程序集和来自 nuget 包的程序集仅供参考,它们不包含真正的逻辑)。您也可以尝试删除 System.Console.dll
上的引用并保留 @"C:\Users\%USERNAME%\.nuget\packages\netstandard.library.0.0\build\netstandard2.0\ref\netstandard.dll"
var dd = typeof(Enumerable).GetTypeInfo().Assembly.Location;
var coreDir = Directory.GetParent(dd);
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "netstandard.dll")
由于您正在使用 typeof(X).Assembly
定位所有其他引用的程序集,这将隐式地 return 将程序集加载到您的应用程序域中。我建议以同样的方式找到匹配的网络标准。但是,由于 netstandard 没有直接定义任何类型,我发现最好的方法是按名称搜索它;
AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "netstandard")