2011-09-29 20 views
50

primero, y para hacer las cosas más claras voy a explicar mi escenario desde la parte superior:Convertir diccionario <cadena, objeto> ¿A objeto anónimo?

tengo un método que tiene la firma siguiente:

public virtual void SendEmail(String from, List<String> recepients, Object model) 

Lo que quiero hacer es generar un objeto anónimo que tiene las propiedades del objeto modelo junto con los dos primeros parámetros también. Aplanar el objeto modelo en PropertyInfo [] es muy sencillo. En consecuencia, pensé en crear un diccionario que contenga los PropertyInfo y los primeros dos params, y luego convertirlo en el objeto anónimo donde la clave es el nombre de la propiedad y el valor es el valor real de la propiedad.

¿Es esto posible? ¿Cualquier otra sugerencia?

+0

¿Cuál es el motivo por el que desea hacer esto? –

+1

Dudo que pueda admitir fácilmente un conjunto arbitrario de valores clave: tendría que construir dinámicamente un nuevo tipo con esas propiedades en tiempo de ejecución. Como solo vas a leerlos, será mejor que crees una sobrecarga que también acepte tu diccionario. – Rup

+0

@Rup: en realidad, esa es una alternativa razonable también. Ya encontré un atajo que funciona bien para mis requisitos pero aún me gustaría saber la respuesta a mi pregunta anterior ... solo por curiosidad :) – Kassem

Respuesta

71

Si realmente desea convertir el diccionario a un objeto que tiene los elementos de la diccionario como propiedades, puede utilizar 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; 

Pero no estoy seguro de cómo se hace eso va a ayudar tú.

+1

¡Es un buen truco! –

+1

tenga en cuenta que esta solución solo está disponible en .NET 4.5 – FlavorScape

+11

@FlavorScape Eso no es cierto, también funciona en .Net 4.0. – svick

4

Los objetos anónimos son los generados por el compilador. No puede generar dinámicamente crear uno. Por otro lado, puedes emitir ese objeto, pero realmente no creo que sea una buena idea.

¿Puede ser que pueda probar objetos dinámicos? El resultado será un objeto con todas las propiedades que necesita.

+1

Sí, un objeto dinámico sería perfecto. Cuidado de dar un ejemplo? – Kassem

+1

No creo que esta sea una afirmación precisa, incluso puede crear tipos CLR verdaderos en tiempo de ejecución. No puedo imaginar por qué no pudo crear un tipo anónimo en tiempo de ejecución. –

+2

@ChrisMarisic cita de la respuesta "usted puede emitir tal objeto". Seguro que puede. –

7

Si usted tiene una clase que desea convertir el diccionario también se puede usar lo siguiente para convertir un diccionario para un objeto de esa clase:

class Ejemplo:

public class Properties1 
{ 
    public string Property { get; set; } 
} 

La solución:

JavaScriptSerializer serializer = new JavaScriptSerializer(); 
Dictionary<string, object> dict = new Dictionary<string, object> { { "Property", "foo" } }; 
Properties1 properties = serializer.ConvertToType<Properties1>(dict); 
string value = properties.Property; 

también es posible usar un método como este para construir el objeto a partir de los Dicti onary, obviamente esto también requiere que tengas una clase.

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

Sin embargo, si usted no tiene la clase se puede crear un objeto dinámico de un diccionario de esta manera:

private static dynamic DictionaryToObject(Dictionary<string, object> dict) 
{ 
    IDictionary<string, object> eo = new ExpandoObject() as IDictionary<string, object>; 
    foreach (KeyValuePair<string, object> kvp in dict) 
    { 
     eo.Add(kvp); 
    } 
    return eo; 
} 

Usted puede utilizar de esta manera:

Dictionary<string, object> dict = new Dictionary<string, object> {{ "Property", "foo" }}; 
dynamic properties = DictionaryToObject(dict); 
string value = properties.Property; 
2

Si quiere convertir Dictionary<string, object> en Anonymous System.Object. Puede utilizar este método:

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

así:

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

object obj1 = FromDictToAnonymousObj(dict); 

para obtener su objeto. Dónde AnonymousType código de clase es:

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

Referencia: https://stackoverflow.com/a/29428640/2073920

1

versión ligeramente más modular la respuesta de svick, usando un métodos de extensión par:

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

Me gusta la idea. Sin embargo, no hay 'ICollection.AddRange'. Tendrás que proporcionar AddRange mediante el uso de extensiones o intentar usar 'source.ToList(). ForEach (someObject.Add)' – Nils

+0

¡Vaya, buen truco! Estoy tan acostumbrado a mis métodos de extensión que los tomo por hecho. Actualizará la respuesta. –

3

he tratado de hacer esto en una declaración con una función de reducción (Agregado en Linq). El código siguiente hace lo mismo que la respuesta aceptada:

var dict = new Dictionary<string, object> { { "Property", "foo" } }; 
dynamic eo = dict.Aggregate(new ExpandoObject() as IDictionary<string, Object>, 
          (a, p) => { a.Add(p.Key, p.Value); return a; }); 
string value = eo.Property; 
Cuestiones relacionadas