2
votes

Compilation Roslyn - Comment référencer une bibliothèque de classes .NET Standard 2.0

J'ai créé un projet d'application console (ciblant .NET Core 3.0) et une bibliothèque de classes (ciblant .NET Standard 2.0). L'application console essaie d'utiliser le compilateur Roslyn pour compiler du code C # qui fait référence à la bibliothèque de classes créée précédemment. Je rencontre cependant des problèmes majeurs.

Voici le code de l'application console (notez que la plupart est un exemple de code de https://github.com/joelmartinez/dotnet-core-roslyn-sample/blob/master/Program.cs ):

namespace ClassLibary1
{
    public static class Class1
    {
        public static string DoStuff()
        {
            return "asdfjkl";
        }
    }
}

et le code de la ClassLibrary1 est juste ceci:

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
    }
}

J'ai commenté deux endroits dans le code avec // 1 et // 2. Le premier est la ligne qui introduit le premier problème et provoque l'échec de la compilation. Le deuxième point (actuellement commenté) est une tentative de contourner le premier problème en ajoutant une référence au fichier netstandard.dll (désolé si le chemin n'est pas portable, juste là où je l'ai trouvé sur ma machine), mais il ne corrige rien, n'introduisant que des erreurs plus cryptiques.

Une idée sur l'approche que je devrais adopter pour faire fonctionner ce code?


0 commentaires

3 Réponses :


4
votes

La première erreur se produit car votre bibliothèque référencée cible netstandard et la compilation de l'application console qui fait référence à cette bibliothèque doit faire référence à netstandard.dll pour résoudre correctement tous les types correspondants. Vous devriez donc ajouter une référence à nestandard.dll mais ce n'est pas tout et vous obtenez ici la deuxième erreur.

Lorsque vous essayez de référencer netsandard directement ou par transitive, vous devez fournir le nestandard.dll correspondant à la plate-forme cible. Et ce netstandard aura une énorme quantité de types de transfert vers les types sur la plate-forme cible actuelle. Si vous regardez le @"C:\Users\%USERNAME%\.nuget\packages\netstandard.library\2.0.0\build\netstandard2.0\ref\netstandard.dll" vous constaterez que ce netstandard.dll ne 'ne contient pas les avant mais contient tous les types directement et bien sûr il contient System.Console . (Je pense qu'il contient tous les types directement car il provient du package nuget qui ne dépend d'aucune plate-forme cible, mais vraiment pas sûr pour cela). Et quand vous essayez de l'ajouter et System.Console.dll par typeof(Console).GetTypeInfo().Assembly.Location vous obtenez en fait deux System.Console dans une compilation.

Donc, pour résoudre ce problème ambigu, vous pouvez ajouter netstandard non pas à partir de ce package nuget, mais à partir de votre plate-forme cible actuelle qui a tous les transferts nécessaires. Pour le .netcore30 , par exemple, vous pouvez utiliser netstandard partir de path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\ ( path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\ attention aux assemblys et assemblys du package nuget ci-dessus juste pour le référencement, ils ne contiennent pas de vraie logique). Vous pouvez également essayer de supprimer la référence sur System.Console.dll et conserver la référence sur @"C:\Users\%USERNAME%\.nuget\packages\netstandard.library\2.0.0\build\netstandard2.0\ref\netstandard.dll"


4 commentaires

Wow, cela a fonctionné! Je viens de changer le chemin de référence pour être @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netco‌​reapp3.0\netstandard‌​.dll" plutôt que le chemin qui impliqué ".nuget" dans mon code d'origine. À ce stade, il a pu effectuer la compilation dynamique et exécuter le code. Merci! Existe-t-il une manière plus "portable" d'écrire ce code (pour ne pas avoir à référencer une DLL à un chemin codé en dur)?


@Anssssss, eh bien, la manière "portable" consiste simplement à utiliser quelques cibles et tâches de sdk et msbuild pour différentes cibles de plate-forme pour une résolution correcte dans des cas courants. Par exemple, pour résoudre path_to_dotnet_sdks\packs\Microsoft.NETCore.App.Ref\3.0.0\re‌​f\netcoreapp3.0\ pour netcore30 vous pouvez utiliser github.com/dotnet/sdk/blob/master/src/Tasks/… . Outre pour .netframework461-472 vous devez utiliser Microsoft.NET.Build.Extensions.NETFramework.targets et ses cibles dépendantes, etc.


J'ai juste fait un peu de recherche et je ne vois pas comment utiliser un fichier .targets avec mon code Roslyn ci-dessus. Pourriez-vous me donner un indice (ou un lien)? Alternativement, je pourrais poser cela dans une question distincte (bien que cela soit assez lié à la question d'origine).


@Anssssss, les cibles et les tâches ne sont pas utilisées avec Roslyn (en tant que partie de celles-ci), mais sont utilisées comme "logique" séparée supplémentaire qui permet de résoudre quelques références pour les utiliser plus tard dans la compilation Roslyn. Ces cibles déterminent simplement comment résoudre les références, où elles doivent être trouvées et ainsi de suite. Une fois que vous avez localisé toutes les références nécessaires, vous pouvez les ajouter à la compilation. En outre, si vous avez .proj fichier .proj ou .sln , vous pouvez consulter github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/… . Il peut éviter de résoudre manuellement dans certains cas.



0
votes

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

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


0 commentaires

0
votes

Puisque vous utilisez typeof(X).Assembly pour localiser tous les autres assemblys référencés, qui typeof(X).Assembly implicitement les assemblys chargés dans votre domaine d'application. Je recommanderais de localiser le netstandard correspondant de la même manière. Cependant, étant donné que netstandard ne définit directement aucun type, la meilleure méthode que j'ai trouvée est de le rechercher par nom;

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


0 commentaires