具有字典属性的C#匿名对象

我正在尝试将字典转换为匿名类型,每个键都有一个属性。

我尝试谷歌它,但我能找到的是如何将匿名对象转换为字典。

我的字典看起来像这样:

var dict = new Dictionary { {"Id", "1"}, {"Title", "My title"}, {"Description", "Blah blah blah"}, }; 

我想返回一个看起来像这样的匿名对象。

 var o = new { Id = "1", Title = "My title", Description = "Blah blah blah" }; 

所以我希望它循环遍历字典中的每个keyValuePair,并在对象中为每个键创建一个属性。

我不知道从哪里开始。

请帮忙。

基本上你不能。 匿名类型由编译器创建,因此它们存在于程序集中,其中包含所有属性名称。 (在这种情况下,属性类型不是问题 – 作为实现细节,编译器创建generics类型,然后使用适当的类型参数创建该实例。)

您要求的类型具有在执行时确定的属性 – 这与匿名类型的工作方式不相符。 你必须在执行时基本上使用它来编译代码 – 这将是一个痛苦,因为它将在不同的程序集中,并且匿名类型是内部的……

也许您应该使用ExpandoObject代替? 然后使用dynamic任何东西都能正常访问属性。

现在,至少每月一次有人询问如何在运行时创建匿名类型……以下是响应:

 using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; using System.Threading; ///  /// The code generated should be nearly equal to the one generated by /// csc 12.0.31101.0 when compiling with /optimize+ /debug-. The main /// difference is in the GetHashCode() (the base init_hash used is /// compiler-dependant) and in the maxstack of the generated methods. /// Note that Roslyn (at least the one present at /// tryroslyn.azurewebsites.net) generates different code for anonymous /// types. ///  public static class AnonymousType { private static readonly ConcurrentDictionary GeneratedTypes = new ConcurrentDictionary(); private static readonly AssemblyBuilder AssemblyBuilder; private static readonly ModuleBuilder ModuleBuilder; private static readonly string FileName; // Some objects we cache private static readonly CustomAttributeBuilder CompilerGeneratedAttributeBuilder = new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes), new object[0]); private static readonly CustomAttributeBuilder DebuggerBrowsableAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerBrowsableAttribute).GetConstructor(new[] { typeof(DebuggerBrowsableState) }), new object[] { DebuggerBrowsableState.Never }); private static readonly CustomAttributeBuilder DebuggerHiddenAttributeBuilder = new CustomAttributeBuilder(typeof(DebuggerHiddenAttribute).GetConstructor(Type.EmptyTypes), new object[0]); private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(Type.EmptyTypes); private static readonly MethodInfo ObjectToString = typeof(object).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes); private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod("Append", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null); private static readonly MethodInfo StringBuilderAppendObject = typeof(StringBuilder).GetMethod("Append", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(object) }, null); private static readonly Type EqualityComparer = typeof(EqualityComparer<>); private static readonly Type EqualityComparerGenericArgument = EqualityComparer.GetGenericArguments()[0]; private static readonly MethodInfo EqualityComparerDefault = EqualityComparer.GetMethod("get_Default", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null); private static readonly MethodInfo EqualityComparerEquals = EqualityComparer.GetMethod("Equals", BindingFlags.Instance | BindingFlags.Public, null, new[] { EqualityComparerGenericArgument, EqualityComparerGenericArgument }, null); private static readonly MethodInfo EqualityComparerGetHashCode = EqualityComparer.GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public, null, new[] { EqualityComparerGenericArgument }, null); private static int Index = -1; static AnonymousType() { var assemblyName = new AssemblyName("AnonymousTypes"); FileName = assemblyName.Name + ".dll"; AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); ModuleBuilder = AssemblyBuilder.DefineDynamicModule("AnonymousTypes", FileName); } public static void Dump() { AssemblyBuilder.Save(FileName); } ///  /// ///  ///  ///  ///  public static Type CreateType(Type[] types, string[] names) { if (types == null) { throw new ArgumentNullException("types"); } if (names == null) { throw new ArgumentNullException("names"); } if (types.Length != names.Length) { throw new ArgumentException("names"); } // Anonymous classes are generics based. The generic classes // are distinguished by number of parameters and name of // parameters. The specific types of the parameters are the // generic arguments. We recreate this by creating a fullName // composed of all the property names, separated by a "|" string fullName = string.Join("|", names.Select(x => Escape(x))); Type type; if (!GeneratedTypes.TryGetValue(fullName, out type)) { // We create only a single class at a time, through this lock // Note that this is a variant of the double-checked locking. // It is safe because we are using a thread safe class. lock (GeneratedTypes) { if (!GeneratedTypes.TryGetValue(fullName, out type)) { int index = Interlocked.Increment(ref Index); string name = names.Length != 0 ? string.Format("<>f__AnonymousType{0}`{1}", index, names.Length) : string.Format("<>f__AnonymousType{0}", index); TypeBuilder tb = ModuleBuilder.DefineType(name, TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit); tb.SetCustomAttribute(CompilerGeneratedAttributeBuilder); GenericTypeParameterBuilder[] generics = null; if (names.Length != 0) { string[] genericNames = Array.ConvertAll(names, x => string.Format("<{0}>j__TPar", x)); generics = tb.DefineGenericParameters(genericNames); } else { generics = new GenericTypeParameterBuilder[0]; } // .ctor ConstructorBuilder constructor = tb.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig, CallingConventions.HasThis, generics); constructor.SetCustomAttribute(DebuggerHiddenAttributeBuilder); ILGenerator ilgeneratorConstructor = constructor.GetILGenerator(); ilgeneratorConstructor.Emit(OpCodes.Ldarg_0); ilgeneratorConstructor.Emit(OpCodes.Call, ObjectCtor); var fields = new FieldBuilder[names.Length]; // There are two for cycles because we want to have // all the getter methods before all the other // methods for (int i = 0; i < names.Length; i++) { // field fields[i] = tb.DefineField(string.Format("<{0}>i__Field", names[i]), generics[i], FieldAttributes.Private | FieldAttributes.InitOnly); fields[i].SetCustomAttribute(DebuggerBrowsableAttributeBuilder); // .ctor constructor.DefineParameter(i + 1, ParameterAttributes.None, names[i]); ilgeneratorConstructor.Emit(OpCodes.Ldarg_0); if (i == 0) { ilgeneratorConstructor.Emit(OpCodes.Ldarg_1); } else if (i == 1) { ilgeneratorConstructor.Emit(OpCodes.Ldarg_2); } else if (i == 2) { ilgeneratorConstructor.Emit(OpCodes.Ldarg_3); } else if (i < 255) { ilgeneratorConstructor.Emit(OpCodes.Ldarg_S, (byte)(i + 1)); } else { // Ldarg uses a ushort, but the Emit only // accepts short, so we use a unchecked(...), // cast to short and let the CLR interpret it // as ushort ilgeneratorConstructor.Emit(OpCodes.Ldarg, unchecked((short)(i + 1))); } ilgeneratorConstructor.Emit(OpCodes.Stfld, fields[i]); // getter MethodBuilder getter = tb.DefineMethod(string.Format("get_{0}", names[i]), MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, CallingConventions.HasThis, generics[i], Type.EmptyTypes); ILGenerator ilgeneratorGetter = getter.GetILGenerator(); ilgeneratorGetter.Emit(OpCodes.Ldarg_0); ilgeneratorGetter.Emit(OpCodes.Ldfld, fields[i]); ilgeneratorGetter.Emit(OpCodes.Ret); PropertyBuilder property = tb.DefineProperty(names[i], PropertyAttributes.None, CallingConventions.HasThis, generics[i], Type.EmptyTypes); property.SetGetMethod(getter); } // ToString() MethodBuilder toString = tb.DefineMethod("ToString", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(string), Type.EmptyTypes); toString.SetCustomAttribute(DebuggerHiddenAttributeBuilder); ILGenerator ilgeneratorToString = toString.GetILGenerator(); ilgeneratorToString.DeclareLocal(typeof(StringBuilder)); ilgeneratorToString.Emit(OpCodes.Newobj, StringBuilderCtor); ilgeneratorToString.Emit(OpCodes.Stloc_0); // Equals MethodBuilder equals = tb.DefineMethod("Equals", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(bool), new[] { typeof(object) }); equals.SetCustomAttribute(DebuggerHiddenAttributeBuilder); equals.DefineParameter(1, ParameterAttributes.None, "value"); ILGenerator ilgeneratorEquals = equals.GetILGenerator(); ilgeneratorEquals.DeclareLocal(tb); ilgeneratorEquals.Emit(OpCodes.Ldarg_1); ilgeneratorEquals.Emit(OpCodes.Isinst, tb); ilgeneratorEquals.Emit(OpCodes.Stloc_0); ilgeneratorEquals.Emit(OpCodes.Ldloc_0); Label equalsLabel = ilgeneratorEquals.DefineLabel(); // GetHashCode() MethodBuilder getHashCode = tb.DefineMethod("GetHashCode", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, CallingConventions.HasThis, typeof(int), Type.EmptyTypes); getHashCode.SetCustomAttribute(DebuggerHiddenAttributeBuilder); ILGenerator ilgeneratorGetHashCode = getHashCode.GetILGenerator(); ilgeneratorGetHashCode.DeclareLocal(typeof(int)); if (names.Length == 0) { ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4_0); } else { // As done by Roslyn // Note that initHash can vary, because // string.GetHashCode() isn't "stable" for // different compilation of the code int initHash = 0; for (int i = 0; i < names.Length; i++) { initHash = unchecked(initHash * (-1521134295) + fields[i].Name.GetHashCode()); } // Note that the CSC seems to generate a // different seed for every anonymous class ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, initHash); } for (int i = 0; i < names.Length; i++) { // Equals() Type equalityComparerT = EqualityComparer.MakeGenericType(generics[i]); MethodInfo equalityComparerTDefault = TypeBuilder.GetMethod(equalityComparerT, EqualityComparerDefault); MethodInfo equalityComparerTEquals = TypeBuilder.GetMethod(equalityComparerT, EqualityComparerEquals); ilgeneratorEquals.Emit(OpCodes.Brfalse_S, equalsLabel); ilgeneratorEquals.Emit(OpCodes.Call, equalityComparerTDefault); ilgeneratorEquals.Emit(OpCodes.Ldarg_0); ilgeneratorEquals.Emit(OpCodes.Ldfld, fields[i]); ilgeneratorEquals.Emit(OpCodes.Ldloc_0); ilgeneratorEquals.Emit(OpCodes.Ldfld, fields[i]); ilgeneratorEquals.Emit(OpCodes.Callvirt, equalityComparerTEquals); // GetHashCode(); MethodInfo EqualityComparerTGetHashCode = TypeBuilder.GetMethod(equalityComparerT, EqualityComparerGetHashCode); ilgeneratorGetHashCode.Emit(OpCodes.Stloc_0); ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, -1521134295); ilgeneratorGetHashCode.Emit(OpCodes.Ldloc_0); ilgeneratorGetHashCode.Emit(OpCodes.Mul); ilgeneratorGetHashCode.Emit(OpCodes.Call, EqualityComparerDefault); ilgeneratorGetHashCode.Emit(OpCodes.Ldarg_0); ilgeneratorGetHashCode.Emit(OpCodes.Ldfld, fields[i]); ilgeneratorGetHashCode.Emit(OpCodes.Callvirt, EqualityComparerGetHashCode); ilgeneratorGetHashCode.Emit(OpCodes.Add); // ToString() ilgeneratorToString.Emit(OpCodes.Ldloc_0); ilgeneratorToString.Emit(OpCodes.Ldstr, i == 0 ? string.Format("{{ {0} = ", names[i]) : string.Format(", {0} = ", names[i])); ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString); ilgeneratorToString.Emit(OpCodes.Pop); ilgeneratorToString.Emit(OpCodes.Ldloc_0); ilgeneratorToString.Emit(OpCodes.Ldarg_0); ilgeneratorToString.Emit(OpCodes.Ldfld, fields[i]); ilgeneratorToString.Emit(OpCodes.Box, generics[i]); ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendObject); ilgeneratorToString.Emit(OpCodes.Pop); } // .ctor ilgeneratorConstructor.Emit(OpCodes.Ret); // Equals() if (names.Length == 0) { ilgeneratorEquals.Emit(OpCodes.Ldnull); ilgeneratorEquals.Emit(OpCodes.Ceq); ilgeneratorEquals.Emit(OpCodes.Ldc_I4_0); ilgeneratorEquals.Emit(OpCodes.Ceq); } else { ilgeneratorEquals.Emit(OpCodes.Ret); ilgeneratorEquals.MarkLabel(equalsLabel); ilgeneratorEquals.Emit(OpCodes.Ldc_I4_0); } ilgeneratorEquals.Emit(OpCodes.Ret); // GetHashCode() ilgeneratorGetHashCode.Emit(OpCodes.Stloc_0); ilgeneratorGetHashCode.Emit(OpCodes.Ldloc_0); ilgeneratorGetHashCode.Emit(OpCodes.Ret); // ToString() ilgeneratorToString.Emit(OpCodes.Ldloc_0); ilgeneratorToString.Emit(OpCodes.Ldstr, names.Length == 0 ? "{ }" : " }"); ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString); ilgeneratorToString.Emit(OpCodes.Pop); ilgeneratorToString.Emit(OpCodes.Ldloc_0); ilgeneratorToString.Emit(OpCodes.Callvirt, ObjectToString); ilgeneratorToString.Emit(OpCodes.Ret); type = tb.CreateType(); type = GeneratedTypes.GetOrAdd(fullName, type); } } } if (types.Length != 0) { type = type.MakeGenericType(types); } return type; } private static string Escape(string str) { // We escape the \ with \\, so that we can safely escape the // "|" (that we use as a separator) with "\|" str = str.Replace(@"\", @"\\"); str = str.Replace(@"|", @"\|"); return str; } } 

这个庞大的代码块将使用Reflection.Emit在运行时生成一些具有您想要的属性的类(您甚至可以使用在C#中非法的字符)。 这些类将遵循C#编译器12.0.31101.0生成的匿名类的命名约定。 即使在IL水平,它们也应该几乎相等。 所有各种Equals()GetHashCode()ToString()实现为由C#编译器12.0.31101.0实现,所有字段都具有相同的名称,插入CompilerGeneratedAttribute (这在道德上是错误的,我知道:-))等等。 因此,所产生的课程就像一只匿名的鸭子和庸医就像一只匿名的鸭子......它们是非常有用的鸭子:-)(请注意,罗斯林的鸭子是不同的子物种......他们的羽毛有点不同......匿名鸭骗子很容易区分这两个亚种:-))

现在,如果你想问崔老师? 生成的类只能通过reflection来使用,所以它们很难使用,但它们有一个特定的用例,Marc Gravell个性化:.NET框架的某些部分大量使用reflection来渲染对象(例如所有各种数据网格)。 这些部分与dynamic对象不兼容,通常不支持object[] 。 解决方案通常是将数据封装在DataTable ......或者您可以使用此:-)

在您的特定情况下,您可以使用此方法:

 public static object FromDictToAnonymousObj(IDictionary dict) { var types = new Type[dict.Count]; for (int i = 0; i < types.Length; i++) { types[i] = typeof(TValue); } // dictionaries don't have an order, so we force an order based // on the Key var ordered = dict.OrderBy(x => x.Key).ToArray(); string[] names = Array.ConvertAll(ordered, x => x.Key); Type type = AnonymousType.CreateType(types, names); object[] values = Array.ConvertAll(ordered, x => (object)x.Value); object obj = type.GetConstructor(types).Invoke(values); return obj; } 

像这样:

 var dict = new Dictionary { {"Id", "1"}, {"Title", "My title"}, {"Description", "Blah blah blah"}, }; object obj1 = FromDictToAnonymousObj(dict); 

获得你的对象。

基本上,这不是你可以做的事情; 匿名类型仍然拥有幕后所有常用代码:只是你没有看到它。 您可以通过dynamic做同样的事情,但这对于期望reflection等的API不起作用 – 因此,您必须通过密钥访问,因为大多数代码字典访问将比匿名类型更方便或dynamic类型。

以下是如何将其作为ExpandoObject ( 本帖的帮助)

 var dict = new Dictionary { {"Id", "1"}, {"Title", "My title"}, {"Description", "Blah blah blah"}, }; var expando = new ExpandoObject() as IDictionary; foreach(var kvp in dict) expando.Add(kvp.Key, kvp.Value); 

它不是匿名对象,但您可以使用DynamicObject完成此操作! 您可以执行以下操作。

 public class MyExpando : DynamicObject { Dictionary dictionary; public MyExpando() { dictionary = new Dictionary { {"Id", "1"}, {"Title", "My title"}, {"Description", "Blah blah blah"}, }; } public override bool TryGetMember(GetMemberBinder binder, out object result) { return dictionary.TryGetValue(binder.Name, out result); } public override bool TrySetMember(SetMemberBinder binder, object value) { if (!dictionary.ContainsKey(binder.Name)) return false; dictionary[binder.Name] = value; return true; } } 

然后,您的对象具有所需的属性。

 dynamic o = new MyExpando(); Console.WriteLine("Id : " + o.Id + ", Title : " + o.Title + ", Description : " + o.Description); 

当然,缺点是在编译时没有类型检查,也没有检查成员是否在编译时存在。