c# - Roslyn 编译 - 如何引用 .NET Standard 2.0 类库

标签 c# roslyn

我创建了一个控制台应用程序项目(针对 .NET Core 3.0)和一个类库(针对 .NET Standard 2.0)。控制台应用程序尝试使用 Roslyn 编译器来编译一些引用先前创建的类库的 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\2.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\2.0.0\build\netstandard2.0\ref\netstandard.dll"你会发现这个netstandard.dll不包含转发但直接包含所有类型,当然它包含 System.Console . (我认为,它直接包含所有类型,因为它来自不依赖于任何目标平台的 nuget 包,但确实不确定)。当您尝试添加它时 System.Console.dll来自 typeof(Console).GetTypeInfo().Assembly.Location你实际上得到了两个 System.Console在汇编中。

所以要解决这个歧义,你可以添加 netstandard不是来自这个 nuget 包,而是来自你当前的目标平台,它拥有所有需要的转发。对于 .netcore30 ,例如,您可以使用 netstandard来自 path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\ (小心上面的 nuget 包中的这个程序集和程序集仅供引用,它们不包含真正的逻辑)。您也可以尝试删除对 System.Console.dll 的引用。并引用 @"C:\Users\%USERNAME%\.nuget\packages\netstandard.library\2.0.0\build\netstandard2.0\ref\netstandard.dll"

关于c# - Roslyn 编译 - 如何引用 .NET Standard 2.0 类库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58840995/

相关文章:

c# - ConvertNullDate 扩展方法

c# - visual studio 2010 F10/F11 不会中断类成员初始化代码

c# - 为什么 SyntaxNode.ReplaceNode 会更改 SyntaxTree 选项?

c# - Roslyn 抛出不支持语言 'C#'

c# - 如何从 GridView 中删除一行?

c# - 本地数据库,我需要一些例子

.net - Roslyn API 文档

c# - 你会用编译器即服务做什么

c# - 可以避免分配 out 值吗?

c# - 尝试在 Unity 中围绕玩家生成一圈敌人