Generazione dinamica di una classe da tipi recuperati in fase di esecuzione


20

È possibile effettuare le seguenti operazioni in C # (o in qualsiasi altra lingua)?

  1. Sto recuperando i dati da un database. In fase di esecuzione posso calcolare il numero di colonne e tipi di dati delle colonne recuperate.

  2. Successivamente voglio "generare" una classe con questi tipi di dati come campi. Voglio anche archiviare tutti i record che ho recuperato in una raccolta.

Il problema è che voglio eseguire entrambi i passaggi 1 e 2 in fase di esecuzione

È possibile? Attualmente sto usando C # ma, se necessario, posso passare a qualcos'altro.


4
Ne hai davvero bisogno? Puoi sicuramente (A) generare una classe personalizzata come altri hanno sottolineato, ma devi anche (B) sapere come usarla in fase di esecuzione. La parte (B) mi sembra anche molto lavoro. Cosa c'è di sbagliato nel mantenere i dati all'interno dell'oggetto DataSet o una sorta di raccolta come il dizionario? Cosa stai cercando di fare?
Giobbe

Assicurati di dare un'occhiata al lavoro svolto da Rob Conery dynamicin Massive: blog.wekeroad.com/helpy-stuff/and-i-shall-call-it-massive
Robert Harvey,

1
Python consente la dichiarazione di classe dinamica, ed infatti è comune. C'era un tutorial di David Mertz intorno al 2001 (l'ho cercato ma non sono riuscito a trovare il link esatto). È semplice.
smci,

@RobertHarvey Il link che hai condiviso è morto. Sai dove posso trovarlo?
Joze,

1
Sebbene tecnicamente possibile, dovrei mettere in discussione il valore di farlo. Il punto di digitazione e classi forti è che in fase di compilazione è possibile utilizzare le informazioni sulle classi per verificare la presenza di errori di sintassi (la nuova generazione di JITer dinamici può ottimizzare senza questo). Chiaramente, provare a usare la tipizzazione dinamica in un ambiente fortemente tipizzato significherebbe perdere tutti i vantaggi di entrambi i paradigmi ...
ArTs

Risposte:


28

Usa CodeDom. Ecco qualcosa per iniziare

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

Crea un assembly "Test.Dynamic.dll" con questa classe al suo interno

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

È inoltre possibile utilizzare le funzionalità dinamiche di C #

Classe DynamicEntity, non è necessario creare nulla in fase di esecuzione

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

E usalo in questo modo

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;

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.