[C#] 동적으로 클래스를 만드는 방법은 무엇입니까?

다음과 같은 수업이 있습니다.

public class Field
{
    public string FieldName;
    public string FieldType;
}

그리고 List<Field>값이 있는 객체 :

{"EmployeeID","int"},
{"EmployeeName","String"},
{"Designation","String"}

다음과 같은 클래스를 만들고 싶습니다.

Class DynamicClass
{
    int EmployeeID,
    String EmployeeName,
    String Designation
}

이것을 할 수있는 방법이 있습니까?

런타임에 이것을 생성하고 싶습니다. 파일 시스템에 실제 CS 파일이 있으면 안됩니다.



답변

예, System.Reflection.Emit네임 스페이스를 사용할 수 있습니다 . 경험이 없다면 간단하지 않지만 확실히 가능합니다.

편집 : 이 코드는 결함이있을 수 있지만 일반적인 아이디어를 제공하고 목표를 향해 좋은 출발을 할 수 있기를 바랍니다.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace TypeBuilderNamespace
{
    public static class MyTypeBuilder
    {
        public static void CreateNewObject()
        {
            var myType = CompileResultType();
            var myObject = Activator.CreateInstance(myType);
        }
        public static Type CompileResultType()
        {
            TypeBuilder tb = GetTypeBuilder();
            ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

            // NOTE: assuming your list contains Field objects with fields FieldName(string) and FieldType(Type)
            foreach (var field in yourListOfFields)
                CreateProperty(tb, field.FieldName, field.FieldType);

            Type objectType = tb.CreateType();
            return objectType;
        }

        private static TypeBuilder GetTypeBuilder()
        {
            var typeSignature = "MyDynamicType";
            var an = new AssemblyName(typeSignature);
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
                    TypeAttributes.Public |
                    TypeAttributes.Class |
                    TypeAttributes.AutoClass |
                    TypeAttributes.AnsiClass |
                    TypeAttributes.BeforeFieldInit |
                    TypeAttributes.AutoLayout,
                    null);
            return tb;
        }

        private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
            ILGenerator getIl = getPropMthdBldr.GetILGenerator();

            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getIl.Emit(OpCodes.Ret);

            MethodBuilder setPropMthdBldr =
                tb.DefineMethod("set_" + propertyName,
                  MethodAttributes.Public |
                  MethodAttributes.SpecialName |
                  MethodAttributes.HideBySig,
                  null, new[] { propertyType });

            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();

            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Stfld, fieldBuilder);

            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }
    }
}


답변

약간의 작업이 필요하지만 확실히 불가능하지는 않습니다.

내가 한 일은 :

  • 문자열로 C # 소스를 만듭니다 (파일에 쓸 필요가 없음).
  • Microsoft.CSharp.CSharpCodeProvider(CompileAssemblyFromSource)를 통해 실행하십시오.
  • 생성 된 유형 찾기
  • 그리고 해당 유형의 인스턴스를 만듭니다 ( Activator.CreateInstance).

이 방법으로 MSIL을 방출하지 않고 이미 알고있는 C # 코드를 처리 할 수 ​​있습니다.

그러나 이것은 클래스가 일부 인터페이스를 구현하거나 일부 기본 클래스에서 파생 된 경우 가장 잘 작동합니다. 그렇지 않으면 호출 코드 (읽기 : 컴파일러)가 런타임에 생성 될 클래스에 대해 어떻게 알 수 있습니까?


답변

DynamicObject 를 사용하여 클래스를 동적으로 만들 수도 있습니다 .

public class DynamicClass : DynamicObject
{
    private Dictionary<string, KeyValuePair<Type, object>> _fields;

    public DynamicClass(List<Field> fields)
    {
        _fields = new Dictionary<string, KeyValuePair<Type, object>>();
        fields.ForEach(x => _fields.Add(x.FieldName,
            new KeyValuePair<Type, object>(x.FieldType, null)));
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        if (_fields.ContainsKey(binder.Name))
        {
            var type = _fields[binder.Name].Key;
            if (value.GetType() == type)
            {
                _fields[binder.Name] = new KeyValuePair<Type, object>(type, value);
                return true;
            }
            else throw new Exception("Value " + value + " is not of type " + type.Name);
        }
        return false;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = _fields[binder.Name].Value;
        return true;
    }
}

모든 클래스 필드를 _fields유형과 값과 함께 사전에 저장 합니다. 두 가지 방법 모두 일부 속성 값을 가져 오거나 설정할 수 있습니다. dynamic이 클래스의 인스턴스를 작성 하려면 키워드를 사용해야합니다 .

귀하의 예와 사용법 :

var fields = new List<Field>() {
    new Field("EmployeeID", typeof(int)),
    new Field("EmployeeName", typeof(string)),
    new Field("Designation", typeof(string))
};

dynamic obj = new DynamicClass(fields);

//set
obj.EmployeeID = 123456;
obj.EmployeeName = "John";
obj.Designation = "Tech Lead";

obj.Age = 25;             //Exception: DynamicClass does not contain a definition for 'Age'
obj.EmployeeName = 666;   //Exception: Value 666 is not of type String

//get
Console.WriteLine(obj.EmployeeID);     //123456
Console.WriteLine(obj.EmployeeName);   //John
Console.WriteLine(obj.Designation);    //Tech Lead

편집 : 그리고 내 수업 모습은 다음 과 같습니다 Field.

public class Field
{
    public Field(string name, Type type)
    {
        this.FieldName = name;
        this.FieldType = type;
    }

    public string FieldName;

    public Type FieldType;
}


답변

나는이 오래된 작업을 다시 열었지만 c # 4.0을 사용하면이 작업이 절대적으로 고통스럽지 않습니다.

dynamic expando = new ExpandoObject();
expando.EmployeeID=42;
expando.Designation="unknown";
expando.EmployeeName="curt"

//or more dynamic
AddProperty(expando, "Language", "English");

자세한 내용은 https://www.oreilly.com/learning/building-c-objects-dynamically를 참조
하십시오


답변

그런 동적 클래스의 의도 된 사용법을 모르고 코드 생성 및 런타임 컴파일을 수행 할 수 있지만 약간의 노력이 필요합니다. 어쩌면 익명 유형 이 도움이 될 것입니다.

var v = new { EmployeeID = 108, EmployeeName = "John Doe" };


답변

CodeDOM 을보고 싶습니다 . 코드 요소를 정의하고 컴파일 할 수 있습니다. 인용 MSDN :

…이 객체 그래프는 지원되는 프로그래밍 언어에 대한 CodeDOM 코드 생성기를 사용하여 소스 코드로 렌더링 할 수 있습니다. CodeDOM을 사용하여 소스 코드를 이진 어셈블리로 컴파일 할 수도 있습니다.


답변

@danijels의 답변에 따라 VB.NET에서 동적으로 클래스를 만듭니다.

Imports System.Reflection
Imports System.Reflection.Emit

Public Class ObjectBuilder

Public Property myType As Object
Public Property myObject As Object

Public Sub New(fields As List(Of Field))
    myType = CompileResultType(fields)
    myObject = Activator.CreateInstance(myType)
End Sub

Public Shared Function CompileResultType(fields As List(Of Field)) As Type
    Dim tb As TypeBuilder = GetTypeBuilder()
    Dim constructor As ConstructorBuilder = tb.DefineDefaultConstructor(MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName)

    For Each field In fields
        CreateProperty(tb, field.Name, field.Type)
    Next

    Dim objectType As Type = tb.CreateType()
    Return objectType
End Function

Private Shared Function GetTypeBuilder() As TypeBuilder
    Dim typeSignature = "MyDynamicType"
    Dim an = New AssemblyName(typeSignature)
    Dim assemblyBuilder As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run)
    Dim moduleBuilder As ModuleBuilder = assemblyBuilder.DefineDynamicModule("MainModule")
    Dim tb As TypeBuilder = moduleBuilder.DefineType(typeSignature, TypeAttributes.[Public] Or TypeAttributes.[Class] Or TypeAttributes.AutoClass Or TypeAttributes.AnsiClass Or TypeAttributes.BeforeFieldInit Or TypeAttributes.AutoLayout, Nothing)
    Return tb
End Function

Private Shared Sub CreateProperty(tb As TypeBuilder, propertyName As String, propertyType As Type)
    Dim fieldBuilder As FieldBuilder = tb.DefineField("_" & propertyName, propertyType, FieldAttributes.[Private])

    Dim propertyBuilder As PropertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, Nothing)
    Dim getPropMthdBldr As MethodBuilder = tb.DefineMethod("get_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, propertyType, Type.EmptyTypes)
    Dim getIl As ILGenerator = getPropMthdBldr.GetILGenerator()

    getIl.Emit(OpCodes.Ldarg_0)
    getIl.Emit(OpCodes.Ldfld, fieldBuilder)
    getIl.Emit(OpCodes.Ret)

    Dim setPropMthdBldr As MethodBuilder = tb.DefineMethod("set_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, Nothing, {propertyType})

    Dim setIl As ILGenerator = setPropMthdBldr.GetILGenerator()
    Dim modifyProperty As Label = setIl.DefineLabel()
    Dim exitSet As Label = setIl.DefineLabel()

    setIl.MarkLabel(modifyProperty)
    setIl.Emit(OpCodes.Ldarg_0)
    setIl.Emit(OpCodes.Ldarg_1)
    setIl.Emit(OpCodes.Stfld, fieldBuilder)

    setIl.Emit(OpCodes.Nop)
    setIl.MarkLabel(exitSet)
    setIl.Emit(OpCodes.Ret)

    propertyBuilder.SetGetMethod(getPropMthdBldr)
    propertyBuilder.SetSetMethod(setPropMthdBldr)
End Sub

End Class