【问题标题】:Roslyn compilation - how to reference a .NET Standard 2.0 class libraryRoslyn 编译 - 如何引用 .NET Standard 2.0 类库
【发布时间】:2023-03-06 00:05:01
【问题描述】:

我创建了一个控制台应用程序项目(面向 .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 文件的引用来解决第一个问题(抱歉,如果路径不可移植,就在我碰巧在我的机器上找到它的地方),但是它不能解决任何问题,只会引入更多神秘错误。

对我应该采取什么方法来让这段代码工作有什么想法吗?

【问题讨论】:

    标签: c# roslyn


    【解决方案1】:

    发生第一个错误是因为您引用的库以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 包,但真的不确定)。当您尝试通过typeof(Console).GetTypeInfo().Assembly.Location 添加它和System.Console.dll 时,您实际上在编译中得到了两个System.Console

    因此,要解决这个模棱两可的问题,您可以添加 netstandard,而不是从这个 nuget 包中添加,而是从您当前的目标平台添加,该平台具有所有需要的转发。例如,对于.netcore30,您可以使用path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\ 中的netstandard(请注意上面的nuget 包中的这些程序集和程序集仅供参考,它们不包含真正的逻辑)。您也可以尝试删除System.Console.dll 上的引用并保留@"C:\Users\%USERNAME%\.nuget\packages\netstandard.library\2.0.0\build\netstandard2.0\ref\netstandard.dll" 上的引用

    【讨论】:

    • 哇,成功了!我只是将引用路径更改为@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\netstandard.dll",而不是原始代码中涉及“.nuget”的路径。那时它能够进行动态编译并运行代码。谢谢!是否有一种更“可移植”的方式来编写该代码(这样我就不必在某些硬编码路径中引用 dll)?
    • @Anssssss,好吧,“便携式”方式只是使用来自 sdk 和 msbuild 的几个目标和任务用于不同的平台目标,以便在常见情况下正确解决。例如,要将path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\ 解析为netcore30,您可以使用github.com/dotnet/sdk/blob/master/src/Tasks/…。除了 .netframework461-472 你应该使用 Microsoft.NET.Build.Extensions.NETFramework.targets 和它依赖的目标等等。
    • 我只是做了一些搜索,没有看到如何在上面的 Roslyn 代码中使用 .targets 文件。你能给我一个提示(或链接)吗?或者,我可以在一个单独的问题中提出这个问题(尽管它与原始问题相当相关)。
    • @Anssssss,目标和任务不与 Roslyn 一起使用(作为它们的一部分),而是用作额外的单独“逻辑”,允许解析几个引用以便稍后在罗斯林汇编。这个目标只是确定如何解析引用,应该在哪里找到它们等等。找到所有需要的参考后,您可以将它们添加到编译中。此外,如果您有.proj 文件或.sln 文件,您可以查看github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/…。在某些情况下,它可能会避免手动解决。
    • 说真的,给乔治多点赞!这个人太聪明了,在我没有正确理解 Roslyn 的几种情况下帮助了我。
    【解决方案2】:

    由于您使用typeof(X).Assembly 来定位所有其他引用的程序集,这将隐式返回加载到您的应用程序域中的程序集。我建议以相同的方式定位匹配的网络标准。但是,由于 netstandard 没有直接定义任何类型,所以我找到的最好的方法是按名称搜索它;

    AppDomain.CurrentDomain.GetAssemblies().Single(a => a.GetName().Name == "netstandard")
    

    【讨论】:

    • 工作就像一个魅力。此解决方案不依赖硬编码的 dll 名称
    • 此解决方案在我的情况下返回:C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.5 如果我使用该文件夹,则无法编译。如果我使用以下程序集,它确实有效:C:\Program Files\dotnet\packs\NETStandard.Library.Ref\2.1.0\ref\netstandard2.1
    【解决方案3】:
    var dd = typeof(Enumerable).GetTypeInfo().Assembly.Location;
    var coreDir = Directory.GetParent(dd);
    
    MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "netstandard.dll")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-20
      • 2018-10-13
      • 1970-01-01
      • 2018-06-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多