264 votes

Comment créer dynamiquement une classe ?

J'ai une classe qui ressemble à ceci :

public class Field
{
    public string FieldName;
    public string FieldType;
}

Et un objet List<Field> avec des valeurs :

{"EmployeeID","int"},
{"EmployeeName","String"},
{"Designation","String"}

Je veux créer une classe qui ressemble à ceci :

Class DynamicClass
{
    int EmployeeID,
    String EmployeeName,
    String Designation
}

Y a-t-il un moyen de le faire ?

Je veux que cela soit généré au moment de l'exécution. Je ne veux pas d'un fichier CS physique résidant dans mon système de fichiers.

4 votes

Voulez-vous utiliser cette classe en cours d'exécution ou seulement générer un fichier ?

0 votes

Je veux qu'il soit généré en cours d'exécution. Je ne veux pas d'un fichier CS physique résidant dans mon système de fichiers. Désolé de ne pas l'avoir mentionné plus tôt.

18 votes

Pouvez-vous nous donner une idée générale de ce que vous avez l'intention de faire avec cette classe ?

344voto

danijels Points 1652

Oui, vous pouvez utiliser System.Reflection.Emit pour cela. Ce n'est pas simple si vous n'avez pas d'expérience en la matière, mais c'est certainement possible.

Edit : Ce code est peut-être imparfait, mais il vous donnera une idée générale et vous permettra, je l'espère, de prendre un bon départ vers votre objectif.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace TypeBuilderNamespace
{
    public static class MyTypeBuilder
    {
        public static void CreateNewObject()
        {
            var myType = CompileResultType();
            var myObject = Activator.CreateInstance(myType);
        }
        public static Type CompileResultType()
        {
            TypeBuilder tb = GetTypeBuilder();
            ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

            // NOTE: assuming your list contains Field objects with fields FieldName(string) and FieldType(Type)
            foreach (var field in yourListOfFields)
                CreateProperty(tb, field.FieldName, field.FieldType);

            Type objectType = tb.CreateType();
            return objectType;
        }

        private static TypeBuilder GetTypeBuilder()
        {
            var typeSignature = "MyDynamicType";
            var an = new AssemblyName(typeSignature);
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
                    TypeAttributes.Public |
                    TypeAttributes.Class |
                    TypeAttributes.AutoClass |
                    TypeAttributes.AnsiClass |
                    TypeAttributes.BeforeFieldInit |
                    TypeAttributes.AutoLayout,
                    null);
            return tb;
        }

        private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
            ILGenerator getIl = getPropMthdBldr.GetILGenerator();

            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getIl.Emit(OpCodes.Ret);

            MethodBuilder setPropMthdBldr =
                tb.DefineMethod("set_" + propertyName,
                  MethodAttributes.Public |
                  MethodAttributes.SpecialName |
                  MethodAttributes.HideBySig,
                  null, new[] { propertyType });

            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();

            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Stfld, fieldBuilder);

            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }
    }
}

3 votes

Génial ! Pouvez-vous également me dire comment créer un objet du type retourné par la méthode CompileResultType() ?

6 votes

Vous pouvez utiliser System.Activator pour cela. Je vais mettre à jour la réponse avec un exemple.

5 votes

Notez également que vous devrez utiliser la réflexion pour examiner, lire et mettre à jour les champs de votre type dynamique. Si vous voulez un intellisense sans réflexion, vous devez avoir une classe ou une interface de base statique dont votre classe dynamique hérite et vers laquelle elle peut être castée. Dans ce cas, vous pouvez modifier la méthode GetTypeBuilder() et changer l'appel moduleBuilder.DefineType pour inclure le type statique comme dernier paramètre (il est nul maintenant).

77voto

Hans Kesting Points 17043

Cela demandera un peu de travail, mais ce n'est certainement pas impossible.

Ce que j'ai fait, c'est :

  • Créez une source C# dans une chaîne de caractères (pas besoin d'écrire dans un fichier),
  • Passez-le dans le Microsoft.CSharp.CSharpCodeProvider (CompileAssemblyFromSource)
  • Trouver le type de produit
  • Et créer une instance de ce type ( Activator.CreateInstance )

De cette façon, vous pouvez vous occuper du code C# que vous connaissez déjà, au lieu de devoir émettre du MSIL.

Mais cela fonctionne mieux si votre classe implémente une interface (ou est dérivée d'une classe de base), sinon comment le code appelant (lire : le compilateur) peut-il connaître la classe qui sera générée au moment de l'exécution ?

8 votes

Vous voudrez peut-être voir cette discussion : réflexion-emit-vs-codedom

2 votes

@nawfal , ashwnacharya, voulait générer dynamiquement à l'exécution sa classe avec ses membres initialement contenus dans une liste. Je ne pense pas que le fait de la mettre dans un fichier, même généré à l'exécution, soit une bonne solution en termes de performances.

18voto

Amittai Shapira Points 2101

Je ne connais pas l'utilisation prévue de ces classes dynamiques, et la génération de code et la compilation au moment de l'exécution peuvent être faites, mais demandent un certain effort. Peut-être Types d'anonymes vous aiderait, quelque chose comme :

var v = new { EmployeeID = 108, EmployeeName = "John Doe" };

9 votes

Vous ne pouvez pas coder en dur les noms des champs. Il les fournit avec son Field.FieldName . Le fait de devoir coder en dur les noms des champs va à l'encontre du but recherché. Si vous devez le faire, vous pouvez tout aussi bien créer la classe.

10voto

Hemant Points 7612

Vous voulez regarder CodeDOM . Il permet de définir des éléments de code et de les compiler. Citation de MSDN :

...Ce graphique d'objets peut être rendu comme suit code source en utilisant un générateur de code CodeDOM CodeDOM pour un langage de programmation de programmation. Le CodeDOM peut également être utilisé pour compiler le code source dans un binaire.

0 votes

Je veux que cela soit généré en cours d'exécution. Je ne veux pas d'un fichier CS physique résidant dans mon système de fichiers. Désolé de ne pas l'avoir mentionné plus tôt.

1 votes

@ashwnacharya : Vous peut utiliser CodeDOM à la fois pour générer le fichier source et pour le compiler au moment de l'exécution !

1 votes

Attention toutefois, le compilateur CodeDOM prend une chaîne de caractères brute, et vous pouvez donc envisager des " attaques par insertion de code " similaires à celles utilisées dans les XSS et les injections SQL.

7voto

Sur la base de la réponse de @danijels, créez dynamiquement une classe en VB.NET :

Imports System.Reflection
Imports System.Reflection.Emit

Public Class ObjectBuilder

Public Property myType As Object
Public Property myObject As Object

Public Sub New(fields As List(Of Field))
    myType = CompileResultType(fields)
    myObject = Activator.CreateInstance(myType)
End Sub

Public Shared Function CompileResultType(fields As List(Of Field)) As Type
    Dim tb As TypeBuilder = GetTypeBuilder()
    Dim constructor As ConstructorBuilder = tb.DefineDefaultConstructor(MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName)

    For Each field In fields
        CreateProperty(tb, field.Name, field.Type)
    Next

    Dim objectType As Type = tb.CreateType()
    Return objectType
End Function

Private Shared Function GetTypeBuilder() As TypeBuilder
    Dim typeSignature = "MyDynamicType"
    Dim an = New AssemblyName(typeSignature)
    Dim assemblyBuilder As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run)
    Dim moduleBuilder As ModuleBuilder = assemblyBuilder.DefineDynamicModule("MainModule")
    Dim tb As TypeBuilder = moduleBuilder.DefineType(typeSignature, TypeAttributes.[Public] Or TypeAttributes.[Class] Or TypeAttributes.AutoClass Or TypeAttributes.AnsiClass Or TypeAttributes.BeforeFieldInit Or TypeAttributes.AutoLayout, Nothing)
    Return tb
End Function

Private Shared Sub CreateProperty(tb As TypeBuilder, propertyName As String, propertyType As Type)
    Dim fieldBuilder As FieldBuilder = tb.DefineField("_" & propertyName, propertyType, FieldAttributes.[Private])

    Dim propertyBuilder As PropertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, Nothing)
    Dim getPropMthdBldr As MethodBuilder = tb.DefineMethod("get_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, propertyType, Type.EmptyTypes)
    Dim getIl As ILGenerator = getPropMthdBldr.GetILGenerator()

    getIl.Emit(OpCodes.Ldarg_0)
    getIl.Emit(OpCodes.Ldfld, fieldBuilder)
    getIl.Emit(OpCodes.Ret)

    Dim setPropMthdBldr As MethodBuilder = tb.DefineMethod("set_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, Nothing, {propertyType})

    Dim setIl As ILGenerator = setPropMthdBldr.GetILGenerator()
    Dim modifyProperty As Label = setIl.DefineLabel()
    Dim exitSet As Label = setIl.DefineLabel()

    setIl.MarkLabel(modifyProperty)
    setIl.Emit(OpCodes.Ldarg_0)
    setIl.Emit(OpCodes.Ldarg_1)
    setIl.Emit(OpCodes.Stfld, fieldBuilder)

    setIl.Emit(OpCodes.Nop)
    setIl.MarkLabel(exitSet)
    setIl.Emit(OpCodes.Ret)

    propertyBuilder.SetGetMethod(getPropMthdBldr)
    propertyBuilder.SetSetMethod(setPropMthdBldr)
End Sub

End Class

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