2011-09-29 10 views
50

pierwszy, a na domiar wyraźniej wytłumaczę mój scenariusz od góry:Konwertuj słownik <ciąg, obiekt> na obiekt anonimowy?

Mam metoda, która ma następujący podpis:

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

Co chcę zrobić jest wygenerować obiekt anonimowy który ma właściwości obiektu modelu wraz z pierwszymi dwoma parametrami. Spłaszczanie obiektu modelu w PropertyInfo [] jest bardzo proste. W związku z tym pomyślałem o stworzeniu Słownika, który przechowuje właściwości PropertyInfo i pierwszych dwóch parametrów, a następnie zostanie przekształcony w anonimowy obiekt, w którym klucz jest nazwą właściwości, a wartość jest rzeczywistą wartością właściwości.

Czy to możliwe? Jakieś inne sugestie?

+0

Jaki jest powód, dla którego chcesz to zrobić? –

+1

Wątpię, czy możesz w łatwy sposób obsługiwać dowolny zestaw wartości kluczy - w czasie wykonywania musiałbyś dynamicznie konstruować nowy typ z tymi właściwościami. Ponieważ zamierzasz je tylko przeczytać, lepiej jest stworzyć przeciążenie, które również akceptuje słownik. – Rup

+0

@Rup: Właściwie to również rozsądna alternatywa. Znalazłem skrót, który działa dobrze na moje potrzeby, ale nadal chciałbym znać odpowiedź na moje pytanie powyżej ... tylko z ciekawości :) – Kassem

Odpowiedz

71

Jeśli naprawdę chcesz przekonwertować słownika do obiektu, który ma elementy słownika jako właściwości, można użyć 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; 

Ale nie jestem pewien, jak robi to pomoże ty.

+1

To miła sztuczka! –

+1

Zauważ, że to rozwiązanie jest dostępne tylko w .NET 4.5 – FlavorScape

+11

@FlavorScape To nie prawda, działa również na .Net 4.0. – svick

4

Obiekty anonimowe to takie, które zostały wygenerowane przez kompilator. Nie można wygenerować dynamicznie, aby je utworzyć. Z drugiej strony możesz emitować taki obiekt, ale naprawdę nie sądzę, że to dobry pomysł.

Może chcesz wypróbować dynamiczne obiekty? Wynik będzie obiektem o wszystkich właściwościach, których potrzebujesz.

+1

Tak, obiekt dynamiczny byłby idealny. Chcesz podać przykład? – Kassem

+1

Nie sądzę, jest to dokładne oświadczenie, można nawet tworzyć prawdziwe typy CLR w czasie wykonywania nie mogę sobie wyobrazić, dlaczego nie można utworzyć anonimowy typ w czasie wykonywania. –

+2

@ChrisMarisic cytat z odpowiedzi "możesz emitować taki obiekt". Oczywiście że możesz. –

7

Jeśli masz klasę chcesz tajnych słownika zbyt, można użyć następujących przekonwertować słownika na obiekt tej klasy:

Przykład klasy:

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

Rozwiązanie:

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

można również użyć metody takie jak to, aby zbudować obiekt z dicti nieugięty, oczywiście wymaga to również posiadania klasy.

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

Jednak jeśli nie masz klasę można utworzyć dynamiczną obiektu ze słownika jak ten:

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

można go używać tak:

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

Jeśli chcesz przekonwertować Dictionary<string, object> do Anonymous System.Object. Można użyć tej metody:

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

tak:

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

object obj1 = FromDictToAnonymousObj(dict); 

aby uzyskać swój obiekt. Gdzie AnonymousType kod klasy jest:

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

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

1

Nieco bardziej modularna wersja odpowiedzi svick jest przy użyciu metody para rozszerzeń:

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

Podoba mi się ten pomysł. Jednak nie ma "ICollection.AddRange". Będziesz musiał dostarczyć AddRange przez użycie rozszerzeń lub spróbować użyć 'source.ToList(). ForEach (someObject.Add)' – Nils

+0

Whoops, dobry haczyk! Jestem tak przyzwyczajony do moich metod rozszerzenia, że ​​biorę je za pewnik. Zaktualizuje odpowiedź. –

3

próbowałem to zrobić w jednej instrukcji z funkcją zmniejszania (Aggregate in Linq). Poniższy kod jest taki sam jak zaakceptowana odpowiedź:

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;