Dynamiczne generowanie klasy na podstawie typów pobieranych w czasie wykonywania

20

Czy można wykonać następujące czynności w języku C # (lub w innym języku)?

  1. Pobieram dane z bazy danych. W czasie wykonywania mogę obliczyć liczbę kolumn i typy danych pobranych kolumn.

  2. Następnie chcę „wygenerować” klasę z tymi typami danych jako polami. Chcę również przechowywać wszystkie rekordy, które pobrałem w kolekcji.

Problem polega na tym, że chcę wykonać krok 1 i 2 w czasie wykonywania

czy to możliwe? Obecnie używam C #, ale w razie potrzeby mogę przejść do czegoś innego.

Chani
źródło
4
Czy naprawdę tego potrzebujesz? Z pewnością możesz (A) wygenerować niestandardową klasę, jak zauważyli inni, ale musisz także (B) wiedzieć, jak jej używać w czasie wykonywania. Część (B) wydaje mi się również bardzo pracochłonna. Co jest złego w przechowywaniu danych w obiekcie DataSet lub jakimś zbiorze, takim jak słownik? Co próbujesz zrobić?
Job
Upewnij się, że masz do obejrzenia prac Rob Conery zrobił ze dynamicw Masywny: blog.wekeroad.com/helpy-stuff/and-i-shall-call-it-massive
Robert Harvey
1
Python pozwala na dynamiczne deklarowanie klas i w rzeczywistości jest powszechne. Był około tutorial Davida Mertza z około 2001 roku (szukałem go, ale nie mogłem znaleźć dokładnego linku). To jest proste.
smci
@RobertHarvey Udostępniony link jest martwy. Czy wiesz gdzie to mogę znaleźć?
Joze
1
Chociaż technicznie jest to możliwe, musiałbym zakwestionować wartość takiego działania. Istotą silnego pisania i klas jest to, że w czasie kompilacji możesz korzystać z informacji o klasie, aby sprawdzić błędy składniowe (bez tego można zoptymalizować nową generację dynamicznych JITerów). Najwyraźniej próba użycia dynamicznego pisania w silnie typowanym środowisku oznaczałaby utratę wszystkich zalet obu paradygmatów ...
ArTs

Odpowiedzi:

28

Użyj CodeDom. Oto coś na początek

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

Tworzy zespół „Test.Dynamic.dll” z tą klasą

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

Możesz także użyć dynamicznych funkcji C #

Klasa DynamicEntity, nie trzeba niczego tworzyć w czasie wykonywania

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

I użyj tego w ten sposób

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
źródło
6

Tak, możesz użyć do tego emitera odbicia . Manning ma coś, co wygląda na świetną książkę na ten temat: Metaprogramowanie w .NET .

BTW: dlaczego nie użyć listy zawierającej słowniki lub podobne do przechowywania każdego rekordu z nazwami pól jako kluczami?

FinnNk
źródło
1
Dziękujemy za odniesienia do Meta-programowania. Oto kilka innych, które znalazłem: vslive.com/Blogs/News-and-Tips/2016/03/...
Leo Gurdian