Dynamisches Generieren einer Klasse aus Typen, die zur Laufzeit abgerufen werden

20

Ist es möglich, Folgendes in C # (oder in einer anderen Sprache) auszuführen?

  1. Ich rufe Daten aus einer Datenbank ab. Zur Laufzeit kann ich die Anzahl der Spalten und Datentypen der abgerufenen Spalten berechnen.

  2. Als nächstes möchte ich eine Klasse mit diesen Datentypen als Felder "generieren". Ich möchte auch alle Datensätze, die ich abgerufen habe, in einer Sammlung speichern.

Das Problem ist, dass ich sowohl Schritt 1 als auch 2 zur Laufzeit ausführen möchte

Ist das möglich? Ich verwende derzeit C #, kann aber bei Bedarf zu etwas anderem wechseln.

Chani
quelle
4
Brauchst du das wirklich Sie können sicher (A) eine benutzerdefinierte Klasse generieren, wie bereits erwähnt, aber Sie müssen auch (B) wissen, wie sie zur Laufzeit verwendet wird. Der Teil (B) scheint mir auch viel Arbeit zu sein. Was ist falsch daran, Daten im DataSet-Objekt oder in einer Sammlung wie einem Wörterbuch zu belassen? Was versuchst du zu machen?
Job
Stellen Sie sicher, dass Sie einen Blick auf die Arbeit werfen, mit der Rob Conery dynamicin Massive gearbeitet hat: blog.wekeroad.com/helpy-stuff/and-i-shall-call-it-massive
Robert Harvey
1
Python ermöglicht eine dynamische Klassendeklaration und ist in der Tat weit verbreitet. Es gab ein Tutorial von David Mertz aus der Zeit um 2001 (ich habe danach gesucht, aber den genauen Link nicht gefunden). Es ist ganz einfach.
SMCI
@RobertHarvey Der Link, den Sie geteilt haben, ist tot. Wissen Sie, wo ich es finden kann?
Joze
1
Obwohl technisch möglich, müsste ich den Wert davon in Frage stellen. Der Punkt starker Typisierung und Klassen ist, dass Sie beim Kompilieren die Klasseninformationen verwenden können, um nach Syntaxfehlern zu suchen (neuere Generationen dynamischer JITer können ohne diese Optimierung arbeiten). Der Versuch, dynamisches Tippen in einer stark typisierten Umgebung zu verwenden, würde
eindeutig

Antworten:

28

Verwenden Sie CodeDom. Hier ist etwas, um anzufangen

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

Es wird eine Assembly "Test.Dynamic.dll" mit dieser Klasse erstellt

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

Sie können auch die dynamischen Funktionen von C # verwenden.

DynamicEntity-Klasse, zur Laufzeit muss nichts erstellt werden

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

Und benutze es so

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
quelle
6

Ja, Sie können Reflection Emit verwenden , um dies zu tun. Manning hat ein hervorragendes Buch herausgebracht: Metaprogrammierung in .NET .

Übrigens: Warum nicht einfach eine Liste mit Wörterbüchern oder ähnlichem verwenden, um jeden Datensatz mit den Feldnamen als Schlüssel zu speichern?

FinnNk
quelle
1
Vielen Dank für die Meta-Programmierreferenzen. Hier sind ein paar andere, die ich gefunden habe: vslive.com/Blogs/News-and-Tips/2016/03/…
Leo Gurdian