215 votes

Est-il possible de compiler et d'exécuter dynamiquement des fragments de code C# ?

Je me demandais s'il était possible d'enregistrer des fragments de code C# dans un fichier texte (ou tout autre flux d'entrée), puis de les exécuter dynamiquement ? En supposant que ce qui m'est fourni se compile bien dans un bloc Main(), est-il possible de compiler et/ou d'exécuter ce code ? Je préférerais le compiler pour des raisons de performances.

À tout le moins, je pourrais définir une interface qu'ils seraient tenus de mettre en œuvre, puis ils fourniraient une "section" de code qui mettrait en œuvre cette interface.

12 votes

Je sais que ce post date de quelques années, mais j'ai pensé qu'il valait la peine d'être mentionné avec l'introduction de la Projet Roslyn En effet, la possibilité de compiler du C# brut à la volée et de l'exécuter dans un programme .NET est un peu plus facile.

201voto

Noldorin Points 67794

La meilleure solution en C#/toutes les langues statiques .NET est d'utiliser la fonction CodeDOM pour de telles choses. (À titre d'information, son autre objectif principal est de construire dynamiquement des bouts de code, ou même des classes entières).

Voici un court exemple tiré de Le blog de LukeH qui utilise aussi un peu de LINQ, juste pour le plaisir.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

class Program
{
    static void Main(string[] args)
    {
        var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } });
        var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll" }, "foo.exe", true);
        parameters.GenerateExecutable = true;
        CompilerResults results = csc.CompileAssemblyFromSource(parameters,
        @"using System.Linq;
            class Program {
              public static void Main(string[] args) {
                var q = from i in Enumerable.Range(1,100)
                          where i % 2 == 0
                          select i;
              }
            }");
        results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));
    }
}

La classe de première importance ici est la CSharpCodeProvider qui utilise le compilateur pour compiler du code à la volée. Si vous souhaitez ensuite exécuter le code, il vous suffit d'utiliser un peu de réflexion pour charger dynamiquement l'assemblage et l'exécuter.

Ici est un autre exemple en C# qui (bien que légèrement moins concis) vous montre précisément comment exécuter le code compilé en cours d'exécution à l'aide de la commande System.Reflection espace de noms.

5 votes

Bien que je doute que vous utilisiez Mono, j'ai pensé qu'il serait utile de signaler qu'il existe un espace de nom Mono.CSharp ( mono-project.com/CSharp_Compiler ) qui contient en fait un compilateur en tant que service afin que vous puissiez exécuter dynamiquement du code de base/évaluer des expressions en ligne, avec un minimum de tracas.

2 votes

Quel serait le besoin réel de faire cela. Je suis assez novice en matière de programmation en général et je pense que c'est cool mais je ne vois pas de raison pour laquelle vous voudriez / ce serait utile. Merci si vous pouvez m'expliquer.

2 votes

@Crash893 : Un système de script pour à peu près n'importe quel type d'application de conception pourrait faire bon usage de ceci. Bien sûr, il existe des alternatives telles que IronPython LUA, mais ceci en est certainement une. Notez qu'un système de plugins serait mieux développé en exposant des interfaces et en chargeant des DLL compilées qui contiennent leurs implémentations, plutôt que de charger du code directement.

42voto

Brian Ensink Points 7579

D'autres ont déjà donné de bonnes réponses sur la façon de générer du code au moment de l'exécution, alors j'ai pensé répondre à votre deuxième paragraphe. J'ai une certaine expérience en la matière et je souhaite simplement partager une leçon que j'ai tirée de cette expérience.

Au minimum, je pourrais définir une interface qu'ils seraient tenus d'implémenter, puis ils fourniraient une "section" de code qui implémente cette interface.

Vous pouvez avoir un problème si vous utilisez un interface comme type de base. Si vous ajoutez une seule nouvelle méthode au interface à l'avenir, toutes les classes existantes fournies par le client qui implémentent la méthode interface deviennent maintenant abstraites, ce qui signifie que vous ne pourrez pas compiler ou instancier la classe fournie par le client au moment de l'exécution.

J'ai rencontré ce problème lorsqu'il s'est agi d'ajouter une nouvelle méthode après environ un an d'utilisation de l'ancienne interface et après avoir distribué une grande quantité de données "héritées" qui devaient être prises en charge. J'ai fini par créer une nouvelle interface qui héritait de l'ancienne, mais cette approche a rendu plus difficile le chargement et l'instanciation des classes fournies par le client, car je devais vérifier quelle interface était disponible.

Une solution à laquelle j'ai pensé à l'époque consistait à utiliser une classe réelle comme type de base, comme dans l'exemple ci-dessous. La classe elle-même peut être marquée abstraite mais toutes les méthodes doivent être des méthodes virtuelles vides (pas des méthodes abstraites). Les clients peuvent alors surcharger les méthodes qu'ils souhaitent et je peux ajouter de nouvelles méthodes à la classe de base sans invalider le code existant fourni par le client.

public abstract class BaseClass
{
    public virtual void Foo1() { }
    public virtual bool Foo2() { return false; }
    ...
}

Indépendamment du fait que ce problème s'applique ou non, vous devez réfléchir à la manière de versionner l'interface entre votre base de code et le code fourni par le client.

2 votes

C'est une perspective précieuse et utile.

8voto

Lawrence Points 1659

Je sais que ce post date de quelques années, mais j'ai pensé qu'il valait la peine d'être mentionné avec l'introduction de la Projet Roslyn En effet, la possibilité de compiler du C# brut à la volée et de l'exécuter dans un programme .NET est un peu plus facile.

3voto

Gary.Ray Points 4042

Pour compiler, vous pourriez simplement lancer un appel shell au compilateur csc. Vous pouvez avoir un mal de tête en essayant de garder vos chemins et interrupteurs droits mais c'est certainement possible.

Exemples de coins de coquille en C#

EDIT : Ou mieux encore, utiliser le CodeDOM comme Noldorin l'a suggéré...

0 votes

Oui, l'avantage de CodeDOM est qu'il peut générer l'assemblage pour vous en mémoire (et fournir des messages d'erreur et d'autres informations dans un format facilement lisible).

3 votes

@Noldorin, L'implémentation de CodeDOM C# ne génère pas réellement un assemblage en mémoire. Vous pouvez activer l'indicateur pour cela, mais il est ignoré. Elle utilise un fichier temporaire à la place.

0 votes

@Matt : Oui, bon point - j'avais oublié ce fait. Néanmoins, cela simplifie grandement le processus (et le rend plus efficace). apparaître comme si l'assemblage était généré en mémoire), et offre une interface gérée complète, ce qui est beaucoup plus agréable que de traiter avec des processus.

1voto

FryGuy Points 5999

L'espace de nom System.CodeDom.Compiler devrait vous aider. Voir cet article

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X