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

7
我创建了一个控制台应用程序项目(目标为.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 文件的引用来解决第一个问题(抱歉路径可能不可移植,只是我在我的机器上找到它的地方),但没有解决任何问题,只引入了更加晦涩的错误。
你有什么想法或建议可以让这段代码工作起来?
4个回答

8
第一个错误是因为您引用的库针对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"的引用。

1
哇,它成功了!我刚刚将引用路径更改为@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\netstandard.dll",而不是原始代码中涉及“.nuget”的路径。此时,它能够进行动态编译并运行代码。谢谢!有没有更“便携”的方法可以编写该代码(这样我就不必引用某个硬编码的路径上的dll)? - Anssssss
1
@Anssssss,嗯,"可移植性" 的方式只是使用 sdk 和 msbuild 中的一些目标和任务来针对不同平台目标进行正确解析。例如,在 netcore30 的情况下,要解析 path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\,您可以使用 https://github.com/dotnet/sdk/blob/master/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets#L274。此外,对于 .netframework461-472,您应该使用 Microsoft.NET.Build.Extensions.NETFramework.targets 及其相关目标等等。 - George Alexandria
1
我刚刚进行了一些搜索,但是并没有找到如何在上述我的Roslyn代码中使用.targets文件。你能给我一个提示(或链接)吗?或者,我可以单独提出这个问题(尽管它与原始问题有相当大的关联)。 - Anssssss
1
@Anssssss,Roslyn不使用targets和tasks(作为它们的一部分),但是作为额外的独立“逻辑”来使用,这允许解析一些引用以便稍后在Roslyn编译中使用。这些目标只是确定如何解析引用,它们应该在哪里找到等等。在定位所有所需的引用之后,您可以将它们添加到编译中。此外,如果您有.proj文件或.sln文件,您可以查看https://github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs。在某些情况下,它可以避免手动解析。 - George Alexandria
认真点,给George更多的赞!这个人非常聪明,在我不理解Roslyn的几种情况下帮助了我。 - feO2x

5

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

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

1
这个解决方案在我的情况下返回: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 - Carlos L. Pérez

0
Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "netstandard.dll")

3
此答案已在低质量队列中进行了审核。以下是如何撰写优质答案的指南。仅包含代码的答案不被认为是好的答案,因为它们对学习社区来说不够有用,很可能会被投票降低并/或删除。 对于你而言一些东西可能是显而易见的,要解释它的作用以及它与现有答案的不同之处,强调其更好之处。来自审核 - Trenton McKinney

0
var dd = typeof(Enumerable).GetTypeInfo().Assembly.Location;
var coreDir = Directory.GetParent(dd);

MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "netstandard.dll")

以下是如何撰写优秀答案的指南:How do I write a good answer?。仅提供代码的答案被认为不是好的答案,而且很可能会被社区学习者们降低评分或删除,因为它们对于学习者来说是不够有用的。这一点对你来说可能很明显,但是请解释它的作用,以及它与现有答案的不同之处和优越性 - Trenton McKinney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接