将 Dictionary<string, object> 转换为匿名对象?
Posted
技术标签:
【中文标题】将 Dictionary<string, object> 转换为匿名对象?【英文标题】:Convert Dictionary<string, object> To Anonymous Object? 【发布时间】:2011-11-27 13:37:09 【问题描述】:首先,为了让事情更清楚,我将从顶部解释我的场景:
我有一个具有以下签名的方法:
public virtual void SendEmail(String from, List<String> recepients, Object model)
我想要做的是生成一个匿名对象,它具有模型对象的属性以及前两个参数。将模型对象展平为 PropertyInfo[] 非常简单。因此,我想创建一个 Dictionary 来保存 PropertyInfo 和前两个参数,然后将其转换为匿名对象,其中键是属性的名称,值是属性的实际值。
这可能吗?还有其他建议吗?
【问题讨论】:
你为什么要这样做? 我怀疑您能否轻松支持任意一组键值 - 您必须在运行时使用这些属性动态构造一个新类型。由于您只是要读回它们,因此最好创建一个也接受您的字典的重载。 @Rup:实际上,这也是一个合理的选择。我已经找到了一条适合我的要求的捷径,但我仍然想知道我上面问题的答案......只是出于好奇:) 查看以下链接,非常好的字典到匿名类型转换的解决方案:jacobcarpenter.wordpress.com/2008/03/13/…tomsundev.wordpress.com/2011/07/20/… 【参考方案1】:匿名对象是编译器为您生成的对象。您不能动态生成创建一个。另一方面,你可以发出这样的对象,但我真的不认为这是个好主意。
您可以尝试动态对象吗?结果将是一个具有您需要的所有属性的对象。
【讨论】:
是的,动态对象将是完美的。愿意举个例子吗? 我不认为这是一个准确的说法,你甚至可以在运行时创建真正的 CLR 类型我无法想象为什么你不能在运行时创建匿名类型。 @ChrisMarisic 引用答案“你可以发出这样的对象”。当然可以。 这不是答案,而是评论。 @JotaBe,它实际上是一个答案,间接地说:“你不能那样做。”我认为这是一个错误的答案,但答案都是一样的。【参考方案2】:如果你真的想将字典转换为以字典项为属性的对象,可以使用ExpandoObject
:
var dict = new Dictionary<string, object> "Property", "foo" ;
var eo = new ExpandoObject();
var eoColl = (ICollection<KeyValuePair<string, object>>)eo;
foreach (var kvp in dict)
eoColl.Add(kvp);
dynamic eoDynamic = eo;
string value = eoDynamic.Property;
【讨论】:
+1 但我不确定这样做对你有什么帮助。 如何在 vb.net 中做同样的事情? 关于你为什么要这样做的问题。将模型传递给 Razor 模板时,使用动态对象比使用字典更容易。因此,如果您有一本字典,您可能希望将其转换为动态对象。然后,在您的 *.cshtml 模板中,占位符如下所示:@Model.Name,而不是:@Model["Name"]。 旁注:ExpandoObject
不适用于反射,因为它不是一个真实的对象,所以如果你碰巧将结果传递给需要一个对象的东西,该对象将不会被反射解决这个问题。您的选择似乎是花哨的动态类型创建(可能是benohead.com/blog/2013/12/26/…)或...查找/开发不需要可以反映的对象的新 API。【参考方案3】:
如果你有一个类也想对字典进行转换,你可以使用以下方法将字典转换为该类的对象:
示例类:
public class Properties1
public string Property get; set;
解决办法:
javascriptSerializer serializer = new JavaScriptSerializer();
Dictionary<string, object> dict = new Dictionary<string, object> "Property", "foo" ;
Properties1 properties = serializer.ConvertToType<Properties1>(dict);
string value = properties.Property;
你也可以使用这样的方法从字典中构建对象,显然这也需要你有一个类。
private static T DictionaryToObject<T>(IDictionary<string, object> dict) where T : new()
T t = new T();
PropertyInfo[] properties = t.GetType().GetProperties();
foreach (PropertyInfo property in properties)
if (!dict.Any(x => x.Key.Equals(property.Name,
StringComparison.InvariantCultureIgnoreCase)))
continue;
KeyValuePair<string, object> item = dict.First(x => x.Key.Equals(property.Name,
StringComparison.InvariantCultureIgnoreCase));
Type tPropertyType = t.GetType().GetProperty(property.Name).PropertyType;
Type newT = Nullable.GetUnderlyingType(tPropertyType) ?? tPropertyType;
object newA = Convert.ChangeType(item.Value, newT);
t.GetType().GetProperty(property.Name).SetValue(t, newA, null);
return t;
但是,如果您没有该类,您可以从字典中创建一个动态对象,如下所示:
private static dynamic DictionaryToObject(Dictionary<string, object> dict)
IDictionary<string, object> eo = (IDictionary<string, object>)new ExpandoObject();
foreach (KeyValuePair<string, object> kvp in dict)
eo.Add(kvp);
return eo;
你可以这样使用它:
Dictionary<string, object> dict = new Dictionary<string, object> "Property", "foo" ;
dynamic properties = DictionaryToObject(dict);
string value = properties.Property;
【讨论】:
【参考方案4】:如果您想将Dictionary<string, object>
转换为匿名System.Object
。你可以使用这个方法:
public static object FromDictToAnonymousObj<TValue>(IDictionary<string, TValue> 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<string, string>
"Id", "1",
"Title", "My title",
"Description", "Blah blah blah",
;
object obj1 = FromDictToAnonymousObj(dict);
获取你的对象。
其中AnonymousType
类代码为:
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;
/// <summary>
/// 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.
/// </summary>
public static class AnonymousType
private static readonly ConcurrentDictionary<string, Type> GeneratedTypes = new ConcurrentDictionary<string, Type>();
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);
/// <summary>
///
/// </summary>
/// <param name="types"></param>
/// <param name="names"></param>
/// <returns></returns>
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__AnonymousType0`1", index, names.Length) : string.Format("<>f__AnonymousType0", 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;
参考:https://***.com/a/29428640/2073920
【讨论】:
dotnet core 中是否有 AnonymousType 的实现 @CruiserKID 我不知道任何类似的实现。尽管您可以像在 .NET 中一样在 .NET Core 中创建和操作匿名类型【参考方案5】:svick 答案的稍微模块化的版本,使用了几个扩展方法:
public static class Extensions
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
foreach (var item in items)
collection.Add(item);
public static dynamic ToDynamicObject(this IDictionary<string, object> source)
ICollection<KeyValuePair<string, object>> someObject = new ExpandoObject();
someObject.AddRange(source);
return someObject;
【讨论】:
我喜欢这个主意。但是,没有ICollection.AddRange
。您必须通过使用扩展来提供 AddRange 或尝试使用 source.ToList().ForEach(someObject.Add)
哎呀,好收获!我已经习惯了我的扩展方法,我认为它们是理所当然的。将更新答案。【参考方案6】:
我尝试在一个带有 reduce 函数的语句中执行此操作(Linq 中的聚合)。下面的代码与接受的答案相同:
var dict = new Dictionary<string, object> "Property", "foo" ;
dynamic eo = dict.Aggregate(new ExpandoObject() as IDictionary<string, Object>,
(a, p) => a.Add(p); return a; );
string value = eo.Property;
【讨论】:
【参考方案7】:这里的功劳归功于已接受的答案。添加这个是因为我想将 List> 变成 List。目的是从数据库表中提取记录。这就是我所做的。
public static List<dynamic> ListDictionaryToListDynamic(List<Dictionary<string,object>> dbRecords)
var eRecords = new List<dynamic>();
foreach (var record in dbRecords)
var eRecord = new ExpandoObject() as IDictionary<string, object>;
foreach (var kvp in record)
eRecord.Add(kvp);
eRecords.Add(eRecord);
return eRecords;
【讨论】:
以上是关于将 Dictionary<string, object> 转换为匿名对象?的主要内容,如果未能解决你的问题,请参考以下文章
如何将 Optional<Dictionary<String, Any>> 转换为 Dictionary<String, Any> 以发送带有 json 参数的 A
C# 将 List<string> 转换为 Dictionary<string, string>
将 XML 转换为 Dictionary<String,String>
WPF MVVM 将 Dictionary<String, List<String>> 绑定到数据网格
将 Dictionary<string, string> 转换为 xml 的简单方法,反之亦然
无法将 Dictionary<string, List<string>> 转换为 IReadOnlyDictionary<string, IReadOnlyCollect