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")