我有一个类,它看起来像这样:

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

和一个对象列表<字段>值:

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

我想创建一个类,看起来像这样:

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

有什么办法可以做到吗?

我希望它在运行时生成。我不希望在文件系统中驻留一个物理CS文件。


当前回答

这需要一些工作,但肯定不是不可能的。

我所做的是:

在字符串中创建c#源代码(不需要写入文件), 通过Microsoft.CSharp.CSharpCodeProvider (CompileAssemblyFromSource)运行它 查找生成的类型 并创建该类型的实例(Activator.CreateInstance)

这样您就可以处理已经知道的c#代码,而不必发出MSIL。

但是,如果你的类实现了一些接口(或从一些基类派生),这是最好的工作,否则调用代码(阅读:编译器)如何知道将在运行时生成的类?

其他回答

运行时代码生成与JVM和CLR - 彼得Sestoft

为真正对这类编程感兴趣的人工作。

我给你的建议是,如果你声明了一些东西,尽量避免字符串,所以如果你有类Field,最好使用类System。类型来存储字段类型而不是字符串。 为了最好的解决方案,而不是创建新的类,尝试使用那些已经创建的FiledInfo,而不是创建新的。

哇!谢谢你的回答!我添加了一些功能,以创建一个“数据表到json”的转换器,我与您分享。

    Public Shared Sub dt2json(ByVal _dt As DataTable, ByVal _sb As StringBuilder)
    Dim t As System.Type

    Dim oList(_dt.Rows.Count - 1) As Object
    Dim jss As New JavaScriptSerializer()
    Dim i As Integer = 0

    t = CompileResultType(_dt)

    For Each dr As DataRow In _dt.Rows
        Dim o As Object = Activator.CreateInstance(t)

        For Each col As DataColumn In _dt.Columns
            setvalue(o, col.ColumnName, dr.Item(col.ColumnName))
        Next

        oList(i) = o
        i += 1
    Next

    jss = New JavaScriptSerializer()
    jss.Serialize(oList, _sb)


End Sub

在"compileresulttype" sub中,我修改了:

    For Each column As DataColumn In _dt.Columns
        CreateProperty(tb, column.ColumnName, column.DataType)
    Next


Private Shared Sub setvalue(ByVal _obj As Object, ByVal _propName As String, ByVal _propValue As Object)
    Dim pi As PropertyInfo
    pi = _obj.GetType.GetProperty(_propName)
    If pi IsNot Nothing AndAlso pi.CanWrite Then
        If _propValue IsNot DBNull.Value Then
            pi.SetValue(_obj, _propValue, Nothing)

        Else
            Select Case pi.PropertyType.ToString
                Case "System.String"
                    pi.SetValue(_obj, String.Empty, Nothing)
                Case Else
                    'let the serialiser use javascript "null" value.
            End Select

        End If
    End If

End Sub

正如Hans所建议的,您可以使用Roslyn动态创建类。

完整的资料来源:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace RoslynDemo1
{
    class Program
    {
        static void Main(string[] args)
        {
            var fields = new List<Field>()
            {
                new Field("EmployeeID","int"),
                new Field("EmployeeName","String"),
                new Field("Designation","String")
            };

            var employeeClass = CreateClass(fields, "Employee");

            dynamic employee1 = Activator.CreateInstance(employeeClass);
            employee1.EmployeeID = 4213;
            employee1.EmployeeName = "Wendy Tailor";
            employee1.Designation = "Engineering Manager";

            dynamic employee2 = Activator.CreateInstance(employeeClass);
            employee2.EmployeeID = 3510;
            employee2.EmployeeName = "John Gibson";
            employee2.Designation = "Software Engineer";

            Console.WriteLine($"{employee1.EmployeeName}");
            Console.WriteLine($"{employee2.EmployeeName}");

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }

        public static Type CreateClass(List<Field> fields, string newClassName, string newNamespace = "Magic")
        {
            var fieldsCode = fields
                                .Select(field => $"public {field.FieldType} {field.FieldName};")
                                .ToString(Environment.NewLine);

            var classCode = $@"
                using System;

                namespace {newNamespace}
                {{
                    public class {newClassName}
                    {{
                        public {newClassName}()
                        {{
                        }}

                        {fieldsCode}
                    }}
                }}
            ".Trim();

            classCode = FormatUsingRoslyn(classCode);


            var assemblies = new[]
            {
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
            };

            /*
            var assemblies = AppDomain
                        .CurrentDomain
                        .GetAssemblies()
                        .Where(a => !string.IsNullOrEmpty(a.Location))
                        .Select(a => MetadataReference.CreateFromFile(a.Location))
                        .ToArray();
            */

            var syntaxTree = CSharpSyntaxTree.ParseText(classCode);

            var compilation = CSharpCompilation
                                .Create(newNamespace)
                                .AddSyntaxTrees(syntaxTree)
                                .AddReferences(assemblies)
                                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            {
                var result = compilation.Emit(ms);
                //compilation.Emit($"C:\\Temp\\{newNamespace}.dll");

                if (result.Success)
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    Assembly assembly = Assembly.Load(ms.ToArray());

                    var newTypeFullName = $"{newNamespace}.{newClassName}";

                    var type = assembly.GetType(newTypeFullName);
                    return type;
                }
                else
                {
                    IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error);

                    foreach (Diagnostic diagnostic in failures)
                    {
                        Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
                    }

                    return null;
                }
            }
        }

        public static string FormatUsingRoslyn(string csCode)
        {
            var tree = CSharpSyntaxTree.ParseText(csCode);
            var root = tree.GetRoot().NormalizeWhitespace();
            var result = root.ToFullString();
            return result;
        }
    }

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

        public Field(string fieldName, string fieldType)
        {
            FieldName = fieldName;
            FieldType = fieldType;
        }
    }

    public static class Extensions
    {
        public static string ToString(this IEnumerable<string> list, string separator)
        {
            string result = string.Join(separator, list);
            return result;
        }
    }
}

是的,您可以为此使用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);
        }
    }
}

我不知道这种动态类的预期用途,代码生成和运行时编译是可以完成的,但需要一些努力。 也许匿名类型会帮助你,比如:

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