我正在尝试将字典转换为匿名类型,每个Key都有一个属性。
我试过google但我能找到的只是如何将匿名对象转换为字典。
我的字典看起来像这样:
var dict = new Dictionary<string, string>
{
{"Id", "1"},
{"Title", "My title"},
{"Description", "Blah blah blah"},
};
我想返回一个看起来像这样的匿名对象。
var o = new
{
Id = "1",
Title = "My title",
Description = "Blah blah blah"
};
所以我希望它循环遍历字典中的每个keyValuePair,并在对象中为每个键创建一个属性。
我不知道从哪里开始。
请帮忙。
答案 0 :(得分:28)
您要求的类型具有在执行时确定的属性 - 这与匿名类型的工作方式不符。你必须在执行时基本上使用它编译代码 - 这将是一个痛苦,因为它将在不同的程序集中,匿名类型是内部...
也许您应该使用ExpandoObject
代替?然后使用dynamic
的任何内容都可以正常访问这些属性。
答案 1 :(得分:13)
现在,每个月至少有一次有人询问如何在运行时创建匿名类型......以下是回复:
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__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
(这在道德上是错误的,我知道:-))等等。所以这些类产生了walk like an anonymous duck和庸医就像一只匿名的鸭子......他们是非常匿名的鸭子:-)(请注意,罗斯林的鸭子是不同的亚种......他们的羽毛有点不同......匿名鸭骗子很容易区分这两个亚种:-))
现在,如果你想问cui prodest?生成的类只能通过反射来使用,所以它们很难使用,但它们有一个特定的用例,Marc Gravell个性化:有部分.NET框架大量使用反射来渲染对象(例如所有各种数据网格)。这些部分与dynamic
个对象不兼容,通常不支持object[]
。解决方案通常是将数据封装在DataTable
中......或者您可以使用: - )
在您的具体情况下,您可以使用此方法:
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);
获取您的对象。
答案 2 :(得分:3)
基本上,这不是你可以做的事情;匿名类型仍然拥有幕后的所有常用代码:只是你没有看到它。 可以通过dynamic
执行相同的操作,但这对于期望反射等的API不起作用 - 因此,您必须按键访问,对于大多数代码字典访问比匿名类型或dynamic
类型更方便。
答案 3 :(得分:2)
以下是ExpandoObject
(来自this post的帮助)
var dict = new Dictionary<string, string>
{
{"Id", "1"},
{"Title", "My title"},
{"Description", "Blah blah blah"},
};
var expando = new ExpandoObject() as IDictionary<string, Object>;
foreach(var kvp in dict)
expando.Add(kvp.Key, kvp.Value);
答案 4 :(得分:1)
它不是一个匿名对象,但您可以使用DynamicObject完成此操作!您可以执行以下操作。
public class MyExpando : DynamicObject
{
Dictionary<string, object> dictionary;
public MyExpando()
{
dictionary = new Dictionary<string, object>
{
{"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);
当然,缺点是在编译时没有类型检查,也没有检查成员是否在编译时存在。