Génération dynamique d'une classe à partir de types récupérés au moment de l'exécution

20

Est-il possible de faire ce qui suit en C # (ou dans tout autre langage)?

  1. Je récupère des données dans une base de données. Au moment de l'exécution, je peux calculer le nombre de colonnes et les types de données des colonnes récupérées.

  2. Ensuite, je veux «générer» une classe avec ces types de données comme champs. Je veux également stocker tous les enregistrements que j'ai récupérés dans une collection.

Le problème est que je veux faire les étapes 1 et 2 à l'exécution

Est-ce possible? J'utilise actuellement C # mais je peux passer à autre chose si besoin.

Chani
la source
4
En avez-vous vraiment besoin? Vous pouvez sûrement (A) générer une classe personnalisée comme d'autres l'ont souligné, mais vous devez également (B) savoir comment l'utiliser au moment de l'exécution. La partie (B) me semble aussi beaucoup de travail. Quel est le problème avec la conservation des données à l'intérieur de l'objet DataSet ou d'une sorte de collection telle que le dictionnaire? Qu'essayez-vous de faire?
Job
Assurez-vous d'avoir regardé le travail que Rob Conery a fait dynamicdans Massive: blog.wekeroad.com/helpy-stuff/and-i-shall-call-it-massive
Robert Harvey
1
Python permet la déclaration de classe dynamique, et en fait, c'est courant. Il y avait un tutoriel de David Mertz datant d'environ 2001 (je l'ai cherché mais je n'ai pas trouvé le lien exact). C'est simple.
smci
@RobertHarvey Le lien que vous avez partagé est mort. Savez-vous où le trouver?
Joze
1
Bien que techniquement possible, je devrais remettre en question la valeur de le faire. Le point fort du typage et des classes est que, au moment de la compilation, vous êtes en mesure d'utiliser les informations de classe pour vérifier les erreurs de syntaxe (une nouvelle génération de JIT dynamiques peut optimiser sans cela). Il est clair qu'en essayant d'utiliser la frappe dynamique dans un environnement fortement typé, vous
perdriez

Réponses:

28

Utilisez CodeDom. Voici quelque chose pour commencer

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

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            string className = "BlogPost";

            var props = new Dictionary<string, Type>() {
                { "Title", typeof(string) },
                { "Text", typeof(string) },
                { "Tags", typeof(string[]) }
            };

            createType(className, props);
        }

        static void createType(string name, IDictionary<string, Type> props)
        {
            var csc = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v4.0" } });
            var parameters = new CompilerParameters(new[] { "mscorlib.dll", "System.Core.dll"}, "Test.Dynamic.dll", false);
            parameters.GenerateExecutable = false;

            var compileUnit = new CodeCompileUnit();
            var ns = new CodeNamespace("Test.Dynamic");
            compileUnit.Namespaces.Add(ns);
            ns.Imports.Add(new CodeNamespaceImport("System"));

            var classType = new CodeTypeDeclaration(name);
            classType.Attributes = MemberAttributes.Public;
            ns.Types.Add(classType);

            foreach (var prop in props)
            {
                var fieldName = "_" + prop.Key;
                var field = new CodeMemberField(prop.Value, fieldName);
                classType.Members.Add(field);

                var property = new CodeMemberProperty();
                property.Attributes = MemberAttributes.Public | MemberAttributes.Final;
                property.Type = new CodeTypeReference(prop.Value);
                property.Name = prop.Key;
                property.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));
                property.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodePropertySetValueReferenceExpression()));
                classType.Members.Add(property);
            }

            var results = csc.CompileAssemblyFromDom(parameters,compileUnit);
            results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));
        }
    }
}

Il crée un assembly 'Test.Dynamic.dll' avec cette classe

namespace Test.Dynamic
{
    public class BlogPost
    {
        private string _Title;
        private string _Text;
        private string[] _Tags;

        public string Title
        {
            get
            {
                return this._Title;
            }
            set
            {
                this._Title = value;
            }
        }
        public string Text
        {
            get
            {
                return this._Text;
            }
            set
            {
                this._Text = value;
            }
        }
        public string[] Tags
        {
            get
            {
                return this._Tags;
            }
            set
            {
                this._Tags = value;
            }
        }
    }
}

Vous pouvez également utiliser les fonctionnalités dynamiques de C #

Classe DynamicEntity, pas besoin de créer quoi que ce soit à l'exécution

public class DynamicEntity : DynamicObject
{
    private IDictionary<string, object> _values;

    public DynamicEntity(IDictionary<string, object> values)
    {
        _values = values;
    }
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return _values.Keys;
    }
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (_values.ContainsKey(binder.Name))
        {
            result = _values[binder.Name];
            return true;
        }
        result = null;
        return false;
    }
}

Et utilisez-le comme ça

var values = new Dictionary<string, object>();
values.Add("Title", "Hello World!");
values.Add("Text", "My first post");
values.Add("Tags", new[] { "hello", "world" });

var post = new DynamicEntity(values);

dynamic dynPost = post;
var text = dynPost.Text;
Mike Koder
la source
6

Oui, vous pouvez utiliser la réflexion émise pour ce faire. Manning a ce qui semble être un excellent livre à ce sujet: la métaprogrammation en .NET .

BTW: pourquoi ne pas simplement utiliser une liste contenant des dictionnaires ou similaires pour stocker chaque enregistrement avec les noms de champ comme clés?

FinnNk
la source
1
Merci pour les références de méta-programmation. Voici quelques autres que j'ai trouvés: vslive.com/Blogs/News-and-Tips/2016/03/…
Leo Gurdian