简体   繁体   中英

C# anonymous object with properties from dictionary

I'm trying to convert an dictionary to an anonymous type with one property for every Key.

I tried google it but all I could find was how to convert a 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 a anonymous object that looks like this.

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

So I would like it to loop thru every keyValuePair in the dictionary and create a property in the object for every key.

I don't know where to begin.

Please help.

You can't, basically. Anonymous types are created by the compiler, so they exist in your assembly with all the property names baked into them. (The property types aren't a problem in this case - as an implementation detail, the compiler creates a generic type and then creates an instance of that using appropriate type arguments.)

You're asking for a type with properties which are determined at execution time - which just doesn't fit with how anonymous types work. You'd have to basically compile code using it at execution time - which would then be a pain as it would be in a different assembly, and anonymous types are internal...

Perhaps you should use ExpandoObject instead? Then anything using dynamic will be able to access the properties as normal.

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

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 big, fat, block of code will use Reflection.Emit to generate at runtime some classes with the properties named however you want (you can even use characters that are illegal in C#). These classes will follow the naming conventions of the anonymous classes generated by the C# compiler 12.0.31101.0 . They should be nearly equal even at the IL level. All the various Equals() , GetHashCode() and ToString() are implemented as implemented by the C# compiler 12.0.31101.0, all the fields have the same name, the CompilerGeneratedAttribute is inserted (this is morally wrong, I know :-) ) and so on. So the classes generated walk like an anonymous duck and quack like an anonymous duck... They are pratically anonymous ducks :-) (note that Roslyn ducks are of a different sub specie... Their plumage is a little different... an anonymous duck connaisseur could easily distinguish the two sub species :-) )

Now, if you want to ask cui prodest? The generated classes can only be used through reflection, so they are difficult to use, but they have a specific use-case, that Marc Gravell individuated: there are parts of the .NET framework that heavily use reflection to render objects (for example all the various datagrids). These parts are incompatible with dynamic objects and often don't support object[] . A solution is often to encapsulate the 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 obtain your object.

Basically, that isn't something that you can do trivially; anonymous types still have all the usual code behind the scenes: just you don't see it. What you can do is the same thing via dynamic , but that won't work for APIs that expect reflection etc - so given that you'd have to be accessing by key, for most code dictionary access will be more convenient than an anonymous type or dynamic type.

Here's how you could do it as an ExpandoObject (help from 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);

It isn't an anonymous object but you can accomplish this with a DynamicObject ! You could 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 is there is no type checking at compile time and no check to see if the member exists either at compile time.

I need it to be able to use enum values in javascript side By using Eval, I have reached a level that will at least work. Maybe it will help you... The url link where I adapted the Eval related codes

public enum ServerTypes { none = 0, uni = 1, home = 2, app = 3 }
public static object ToClass(Type value)
    {
        if (value.IsEnum)
        {
            var info = String.Concat("new {", String.Join(", ", Enum.GetNames(value).Select((x, i) => String.Join(" = ", x, Enum.GetValues(value).GetValue(i).ToInt32().ToString()))), "}");
            var nmspc = typeof(Tools).Namespace;
            var cp = new CompilerParameters();
            cp.ReferencedAssemblies.Add("system.dll");
            cp.CompilerOptions = "/t:library";
            cp.GenerateInMemory = true;
            var sb = new StringBuilder();
            sb.AppendLine(String.Concat("namespace ", nmspc, " { using System; public class CSCodeEvaler { public object EvalCode() { "));
            sb.AppendLine(String.Format("return {0};", info));
            sb.AppendLine(" } } }");
            var cr = new CSharpCodeProvider().CompileAssemblyFromSource(cp, sb.ToString());
            if (cr.Errors.Count > 0) { throw new InvalidExpressionException(String.Format("Error ({0}) evaluating: {1}", cr.Errors[0].ErrorText, info)); }
            else
            {
                var o = cr.CompiledAssembly.CreateInstance(String.Concat(nmspc, ".CSCodeEvaler"));
                return o.GetType().GetMethod("EvalCode").Invoke(o, null);
            }
        }
        else { throw new NotImplementedException("value tipi enum olmalıdır!"); }
    }

And finally result

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM