C # anonymous object with dictionary properties

I am trying to convert a dictionary to an anonymous type with one property for each key.

I tried google, but all I could find was how to convert an anonymous object to a dictionary.

My dictionary looks something like this:

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

And I would like to return an anonymous object that looks like this.

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

Therefore, I would like it to loop through each keyValuePair in the dictionary and create a property in the object for each key.

I don’t know where to start.

Please, help.

+22
object dictionary c #
Apr 02 '15 at 13:15
source share
5 answers

You cannot, in principle. Anonymous types are created by the compiler, so they exist in your assembly with all the property names baked into them. (Property types are not a problem in this case - as a part of the implementation, the compiler creates a generic type and then instantiates it using the appropriate type arguments.)

You request a type with properties that are defined at runtime, which simply is not suitable for anonymous types to work. You will have to basically compile the code using it at runtime, which would be painful, as in another assembly, and the anonymous types are internal ...

Perhaps you should use ExpandoObject instead? Then everything that uses dynamic will be able to access the properties as usual.

+28
Apr 02 '15 at
source share

Now, at least once a month, someone asks how to create an anonymous type at runtime ... Here is the answer:

 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, equalityComparerTDefault); ilgeneratorGetHashCode.Emit(OpCodes.Ldarg_0); ilgeneratorGetHashCode.Emit(OpCodes.Ldfld, fields[i]); ilgeneratorGetHashCode.Emit(OpCodes.Callvirt, EqualityComparerTGetHashCode); 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; } } 

This large, thick block of code will use Reflection.Emit to generate at run time some classes with named properties that you want (you can even use invalid characters in C #). These classes will follow the naming conventions of anonymous classes generated by the C # 12.0.31101.0 compiler. They should be almost equal even at the IL level. All the various Equals() , GetHashCode() and ToString() implemented as implemented by the C # 12.0.31101.0 compiler, all fields have the same name, CompilerGeneratedAttribute inserted (this is morally wrong, I know :-)), and so on. Thus, the generated classes walk like anonymous ducks and quack like anonymous ducks ... They are almost anonymous ducks :-) (note that Roslin ducks belong to a different species ... Their plumage is a little different ... anonymous duck lover can easily distinguish between two subspecies :-))

Now if you want to ask cui prodest? Generated classes can be used only with the help of reflection, therefore they are difficult to use, but they have a special use case that Mark Gravell individualized: there are parts of the .NET Framework that intensively use reflection to visualize objects (for example, all different data networks). These parts are incompatible with dynamic objects and often do not support object[] . The solution is often to encapsulate data in a DataTable ... or you can use this :-)

In your specific case, you can use this method:

 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; } 

like this:

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

to get your object.

+13
Apr 03 '15 at 8:17
source share

Basically, this is not something you can do trivially; anonymous types still have regular code behind the scenes: you just don't see it. What you can do is the same thing through dynamic , but this will not work for APIs that expect reflection, etc. - therefore, given that you will need to access by key, for most code dictionaries, access will be more convenient than anonymous type or dynamic .

+3
Apr 02 '15 at 13:24
source share

Here you can do it as ExpandoObject (help of 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); 
+2
Apr 2 '15 at 13:20
source share

This is not an anonymous object, but you can accomplish this with DynamicObject ! You can do the following.

 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; } } 

Then you have your object with the properties you want.

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

Downside, of course, does not check the type at compile time and does not check if the element exists at compile time.

+2
Apr 02 '15 at 14:50
source share



All Articles