Gerando uma classe dinamicamente a partir de tipos que são buscados em tempo de execução

20

É possível fazer o seguinte em C # (ou em qualquer outro idioma)?

  1. Estou buscando dados de um banco de dados. Em tempo de execução, posso calcular o número de colunas e os tipos de dados das colunas buscadas.

  2. Em seguida, quero "gerar" uma classe com esses tipos de dados como campos. Também quero armazenar todos os registros que busquei em uma coleção.

O problema é que eu quero executar as etapas 1 e 2 em tempo de execução

Isso é possível? Atualmente, estou usando c #, mas posso mudar para outra coisa, se precisar.

Chani
fonte
4
Você realmente precisa disso? Você pode (A) gerar com certeza uma classe personalizada, como outros apontaram, mas também precisa (B) saber como usá-la em tempo de execução. A parte (B) também parece muito trabalho para mim. O que há de errado em manter os dados dentro do objeto DataSet ou algum tipo de coleção, como o dicionário? O que você está tentando fazer?
Certifique-se de ter um olhar para o trabalho Rob Conery fez com dynamicno maciço: blog.wekeroad.com/helpy-stuff/and-i-shall-call-it-massive
Robert Harvey
1
O Python permite a declaração de classe dinâmica e, de fato, é comum. Houve um tutorial de David Mertz por volta de 2001 (procurei, mas não consegui encontrar o link exato). É simples.
smci 17/07/11
@RobertHarvey O link que você compartilhou está morto. Você sabe onde posso encontrá-lo?
Joze
1
Embora tecnicamente possível, eu teria que questionar o valor de fazê-lo. O objetivo da digitação e das classes fortes é que, em tempo de compilação, você possa usar as informações da classe para verificar se há erros de sintaxe (a geração mais recente de JITers dinâmicos pode otimizar sem isso). Claramente, tentando usar tipagem dinâmica no ambiente fortemente tipado significaria que você perde todas as vantagens de ambos paradigmas ...
Artes

Respostas:

28

Use CodeDom. Aqui está algo para começar

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

Ele cria um assembly 'Test.Dynamic.dll' com esta 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;
            }
        }
    }
}

Você também pode usar os recursos dinâmicos do C #

Classe DynamicEntity, sem necessidade de criar nada em tempo de execução

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 use assim

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

Sim, você pode usar emissão de reflexão para fazer isso. Manning tem o que parece ser um excelente livro sobre isso: metaprogramação em .NET .

BTW: por que não usar apenas uma lista contendo dicionários ou similares para armazenar cada registro com os nomes dos campos como chaves?

FinnNk
fonte
1
Obrigado pelas referências de metaprogramação. Aqui estão algumas outras que encontrei: vslive.com/Blogs/News-and-Tips/2016/03/…
Leo Gurdian