web-dev-qa-db-fra.com

Utilisation des fonctionnalités C # 6 avec CodeDomProvider (Roslyn)

CodeDomProvider objCodeCompiler = CodeDomProvider.CreateProvider( "CSharp" );

CompilerParameters objCompilerParameters = new CompilerParameters();

...

CompilerResults objCompileResults = objCodeCompiler.CompileAssemblyFromFile( objCompilerParameters, files.ToArray() );

Lorsque je compile mes fichiers, j'obtiens:

FileFunctions.cs (347): Erreur: caractère inattendu '$'

Est-ce que quelqu'un sait comment faire fonctionner l'interpolation de chaînes avec la compilation CodeDom?

J'ai trouvé ce lien: Comment cibler .net 4.5 avec CSharpCodeProvider?

J'ai donc essayé:

     var providerOptions = new Dictionary<string, string>();
     providerOptions.Add( "CompilerVersion", "v4.0" );

     // Instantiate the compiler.
     CodeDomProvider objCodeCompiler = CodeDomProvider.CreateProvider( "CSharp", providerOptions );

Mais je reçois toujours la même erreur.

J'ai également mis à jour le framework cible vers .NET Framework 4.6.

REMARQUE: je ne peux pas spécifier "v4.5" ou "v4.6" ou j'obtiendrai:

************** Exception Text **************
System.InvalidOperationException: Compiler executable file csc.exe cannot be found.
   at System.CodeDom.Compiler.RedistVersionInfo.GetCompilerPath(IDictionary`2 provOptions, String compilerExecutable)
   at Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch(CompilerParameters options, String[] fileNames)
   at Microsoft.CSharp.CSharpCodeGenerator.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromFileBatch(CompilerParameters options, String[] fileNames)
   at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromFile(CompilerParameters options, String[] fileNames)
   at Dynamic.CodeDOMCompiler.CompileAllCodeFiles() in C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC#\Core\CodeDOMCompiler.cs:line 93
   at NewForm.InitializeSystem() in C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC#\NewForm.cs:line 179
   at NewForm.NewForm_Load(Object sender, EventArgs e) in C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC#\NewForm.cs:line 111
   at System.Windows.Forms.Form.OnLoad(EventArgs e)

J'ai essayé d'utiliser la suggestion de Thomas Levesque:

CodeDomProvider objCodeCompiler = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider();

Mais alors je reçois:

************** Exception Text **************
System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC#\bin\x86\Debug\bin\roslyn\csc.exe'.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at Microsoft.CodeDom.Providers.DotNetCompilerPlatform.Compiler.get_CompilerName()
   at Microsoft.CodeDom.Providers.DotNetCompilerPlatform.Compiler.FromFileBatch(CompilerParameters options, String[] fileNames)
   at Microsoft.CodeDom.Providers.DotNetCompilerPlatform.Compiler.CompileAssemblyFromFileBatch(CompilerParameters options, String[] fileNames)
   at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromFile(CompilerParameters options, String[] fileNames)
   at Dynamic.CodeDOMCompiler.CompileAllCodeFiles() in C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC#\Core\CodeDOMCompiler.cs:line 87
   at NewForm.InitializeSystem() in C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC#\NewForm.cs:line 179
   at NewForm.NewForm_Load(Object sender, EventArgs e) in C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC#\NewForm.cs:line 111
   at System.Windows.Forms.Form.OnLoad(EventArgs e)

Je ne sais pas pourquoi il essaie de rechercher "csc.exe" dans un sous-dossier de mon répertoire bin.

Ce chemin existe:

C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC #\bin\x86\Debug\roslyn

Mais il cherchait:

C:\Users\Derek.Morin\Documents\Visual Studio 2010\Projects\ScriptCode\ScriptCode.ConvertedToC #\bin\x86\Debug\bin\roslyn\csc.exe

34
Derek

Le fournisseur CodeDOM intégré ne prend pas en charge C # 6. Utilisez celui-ci à la place:

https://www.nuget.org/packages/Microsoft.CodeDom.Providers.DotNetCompilerPlatform/

Il est basé sur Roslyn et prend en charge les fonctionnalités C # 6.

Modifiez simplement cette ligne:

CodeDomProvider objCodeCompiler = CodeDomProvider.CreateProvider( "CSharp" );

pour ça:

CodeDomProvider objCodeCompiler = new Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider();
24
Thomas Levesque

Mise à jour: mars 2018

Attention, NuGet version 1.0.6 ... 1.0.8 ne copiera pas le dossier/roslyn dans le répertoire de sortie de la génération sur les projets non Web. Meilleur bâton avec 1.0.5 https://github.com/aspnet/RoslynCodeDomProvider/issues/38

La compilation au moment de l'exécution à l'aide des fonctionnalités C # 6 nécessite un nouveau compilateur, comme l'a mentionné @ thomas-levesque. Ce compilateur peut être installé à l'aide du package nuget Microsoft.CodeDom.Providers.DotNetCompilerPlatform .

Pour les applications de bureau, il y a un problème. L'équipe ASP.NET, dans sa sagesse infinie, a codé en dur le chemin vers le compilateur comme <runtime-directory>\bin\roslyn\csc.exe Voir la discussion sur https://github.com/dotnet/roslyn/issues/948

Si votre application de bureau est compilée en \myapp\app.exe, le compilateur roslyn sera situé à \myapp\roslyn\csc.exe, MAIS LE CSharpCodeProvider RESOLURA csc.exe comme \myapp\bin\roslyn\csc.exe

Autant que je sache, vous avez deux options

  1. Créez une routine de post-construction et/ou d'installation qui déplacera le \roslyn sous-répertoire de \bin\roslyn.
  2. Correction du code d'exécution grâce à la magie noire de réflexion.

Voici # 2, en exposant le CSharpCodeProvider comme une propriété dans une classe utilitaire.

using System.Reflection;
using Microsoft.CodeDom.Providers.DotNetCompilerPlatform;

static Lazy<CSharpCodeProvider> CodeProvider { get; } = new Lazy<CSharpCodeProvider>(() => {
    var csc = new CSharpCodeProvider();
    var settings = csc
        .GetType()
        .GetField("_compilerSettings", BindingFlags.Instance | BindingFlags.NonPublic)
        .GetValue(csc);

    var path = settings
        .GetType()
        .GetField("_compilerFullPath", BindingFlags.Instance | BindingFlags.NonPublic);

    path.SetValue(settings, ((string)path.GetValue(settings)).Replace(@"bin\roslyn\", @"roslyn\"));

    return csc;
});
30
Aaron Hudon

J'ai récemment abordé ce problème. Pour le contexte, j'essayais d'exécuter un projet MSTest sur un projet de bibliothèque en utilisant System.CodeDom, mais cela donnait toujours un compilateur qui implémentait C # 5, que j'aie ou non Microsoft.Net.Compilers ou Microsoft.CodeDom.Providers.DotNetCompilerPlatform packages référencés par le projet sous test.

Ma solution pour cela était:

  • Utiliser le package Microsoft.CodeDom.Providers.DotNetCompilerPlatform
  • Définissez le package PrivateAssets sur contentfiles;analyzers
  • Passe options du fournisseur avec CompilerDirectoryPath défini sur le répertoire copié

valeur par défaut pour PrivateAssets est contentfiles;analyzers;build, donc obtenir des projets de référencement pour copier également le dossier nécessite de supprimer build du paramètre.

Exemple de code:

var compiler = CodeDomProvider.CreateProvider("cs", new Dictionary<string, string> {
    { "CompilerDirectoryPath", Path.Combine(Environment.CurrentDirectory, "roslyn") }
});

Faire fonctionner cela avec Microsoft.Net.Compilers serait un peu plus fastidieux car aucune copie n'est effectuée, mais l'étape finale de pointage de CompilerDirectoryPath vers le dossier tools du package est la même.

4
tychon

Informations mises à jour: même après la publication de FW 4.8, vous ne pouvez toujours pas utiliser toutes les nouvelles fonctionnalités de C # 8.0 - la distribution contient CSC, limitée à la version 5.0; Mais il y a un hack pour utiliser CSC, distribué avec VS2019 (oui, vous devez l'installer):

var csprovider = new CSharpCodeProvider(new Dictionary<string,string> {
    ["CompilerDirectoryPath"] = @"c:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\Roslyn",
});
options += " -langversion:8.0 ";

var par = new CompilerParameters { GenerateInMemory = true, CompilerOptions = options };
par.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
par.ReferencedAssemblies.Add("System.Core.dll");

var res = csprovider.CompileAssemblyFromSource(par, "your C# code");

return res.CompiledAssembly;// <- compiled result

BTW malgré l'option explicite 'GenerateInMemory', votre code sera quand même écrit dans le fichier et alors seulement sera compilé. Gardez à l'esprit si vous souhaitez que votre application s'exécute sans accès au disque.

3
Vincent

Face au même problème du compilateur complètement cassé et trouvé une troisième solution en plus de celles répertoriées dans le réponse d'Aaron , en regardant la source décompilée de la bibliothèque, j'ai trouvé que, avant de définir le chemin codé en dur {ProgramLocation}\bin\roslyn il recherche une variable d'environnement (également codée en dur) pour cet emplacement, et s'il est défini, il l'utilise à la place.

Dans cet esprit, un code comme celui-ci "résoudrait" également le problème:

//Set hardcoded environment variable to set the path to the library
Environment.SetEnvironmentVariable("ROSLYN_COMPILER_LOCATION", "actual compiler location goes here", EnvironmentVariableTarget.Process);
//Create compiler object
CSharpCodeProvider compiler = new CSharpCodeProvider();
//Clean up
Environment.SetEnvironmentVariable("ROSLYN_COMPILER_LOCATION", null, EnvironmentVariableTarget.Process);

//Use "compiler" variable to actually compile the dynamic code

Bien que cela ne recourt pas à la réflexion pour jouer avec les éléments internes, cela dépend toujours des détails de mise en œuvre et abuser des variables d'environnement comme celle-ci semble tout simplement faux. Personnellement, j'aime plus que l'alternative de réflexion, mais en même temps, je sais que les deux s'appuient sur la mise en œuvre exacte (ainsi que sur le chemin codé en dur).

En raison de ce problème et de la nécessité d'appeler un programme externe pour faire ce qui devrait être fait en cours de processus, je considère toujours que cette bibliothèque est complètement endommagée.

1
Alejandro