Objekt dem Wörterbuch zuordnen und umgekehrt

89

Gibt es eine elegante schnelle Möglichkeit, Objekte einem Wörterbuch zuzuordnen und umgekehrt?

Beispiel:

IDictionary<string,object> a = new Dictionary<string,object>();
a["Id"]=1;
a["Name"]="Ahmad";
// .....

wird

SomeClass b = new SomeClass();
b.Id=1;
b.Name="Ahmad";
// ..........
Sawan
quelle
Der schnellste Weg wäre die Codegenerierung wie Protobuf ... Ich habe Ausdrucksbäume verwendet, um Reflexionen so oft wie möglich zu vermeiden
Konrad
Alle unten aufgeführten selbstcodierten Antworten unterstützen keine Tiefenkonvertierung und funktionieren nicht in realen Anwendungen. Sie sollten eine Bibliothek wie Newtonsoft verwenden, wie von Earnerplates vorgeschlagen
Cesar

Antworten:

174

Mit etwas Reflexion und Generika in zwei Erweiterungsmethoden können Sie dies erreichen.

Richtig, andere haben meistens die gleiche Lösung gewählt, aber dies erfordert weniger Reflexion, was leistungsmäßig und viel lesbarer ist:

public static class ObjectExtensions
{
    public static T ToObject<T>(this IDictionary<string, object> source)
        where T : class, new()
    {
            var someObject = new T();
            var someObjectType = someObject.GetType();

            foreach (var item in source)
            {
                someObjectType
                         .GetProperty(item.Key)
                         .SetValue(someObject, item.Value, null);
            }

            return someObject;
    }

    public static IDictionary<string, object> AsDictionary(this object source, BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
    {
        return source.GetType().GetProperties(bindingAttr).ToDictionary
        (
            propInfo => propInfo.Name,
            propInfo => propInfo.GetValue(source, null)
        );

    }
}

class A
{
    public string Prop1
    {
        get;
        set;
    }

    public int Prop2
    {
        get;
        set;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, object> dictionary = new Dictionary<string, object>();
        dictionary.Add("Prop1", "hello world!");
        dictionary.Add("Prop2", 3893);
        A someObject = dictionary.ToObject<A>();

        IDictionary<string, object> objectBackToDictionary = someObject.AsDictionary();
    }
}
Matías Fidemraizer
quelle
Ich mag die Implementierung, ich habe tatsächlich damit begonnen, aber haben Sie Feedback zur Leistung? Ich weiß, dass Reflexion nicht die größte ist, wenn es um Leistung geht? Hast du irgendwelche Kommentare?
Nolimit
@nolimit Eigentlich kenne ich die tatsächliche Leistung nicht, aber ich denke, es ist nicht so schlimm (schlimmer als das Vermeiden von Reflexionen natürlich ...). Was ist die Alternative? Abhängig von den Projektanforderungen gibt es möglicherweise andere Optionen, z. B. die dynamische Eingabe, die weitaus schneller ist als die Reflexion ... Wer weiß! :)
Matías Fidemraizer
In meinem Fall ist die Alternative so einfach wie das Erstellen des Objekts durch Zuweisung, aber ich suche nach einer ordentlichen, aber kostengünstigen Möglichkeit, ein Objekt zu erstellen. Having said that, ich gefunden habe , dies , was bedeutet , zu mir , dass es nicht zu teuer ist dieses Stück Code zu verwenden.
Nolimit
@ Nolimit Ja, es scheint, dass es kein Problem gibt. Es geht nur darum, das richtige Werkzeug für das erforderliche Problem zu verwenden: D In Ihrem Fall sollte es wie ein Zauber funktionieren. Ich glaube, ich kann noch etwas im Code optimieren!
Matías Fidemraizer
@nolimit Überprüfen Sie, ob in der ersten Methode GetType()nicht someObjectmehr für jede Eigenschaft mehr aufgerufen wird.
Matías Fidemraizer
30

Konvertieren Sie das Wörterbuch zuerst mit Newtonsoft in eine JSON-Zeichenfolge.

var json = JsonConvert.SerializeObject(advancedSettingsDictionary, Newtonsoft.Json.Formatting.Indented);

Deserialisieren Sie dann die JSON-Zeichenfolge für Ihr Objekt

var myobject = JsonConvert.DeserializeObject<AOCAdvancedSettings>(json);
Lernerplatten
quelle
1
Einfach und kreativ!
Kamalpreet
1
Verwenden Sie diesen Ansatz mit Vorsicht. Es wird Ihre Anwendung blockieren, wenn es für viele (Hunderte) Wörterbücher verwendet wird.
Amackay11
12

Scheint, als würde Reflexion hier nur helfen. Ich habe ein kleines Beispiel für die Konvertierung eines Objekts in ein Wörterbuch gemacht und umgekehrt:

[TestMethod]
public void DictionaryTest()
{
    var item = new SomeCLass { Id = "1", Name = "name1" };
    IDictionary<string, object> dict = ObjectToDictionary<SomeCLass>(item);
    var obj = ObjectFromDictionary<SomeCLass>(dict);
}

private T ObjectFromDictionary<T>(IDictionary<string, object> dict)
    where T : class 
{
    Type type = typeof(T);
    T result = (T)Activator.CreateInstance(type);
    foreach (var item in dict)
    {
        type.GetProperty(item.Key).SetValue(result, item.Value, null);
    }
    return result;
}

private IDictionary<string, object> ObjectToDictionary<T>(T item)
    where T: class
{
    Type myObjectType = item.GetType();
    IDictionary<string, object> dict = new Dictionary<string, object>();
    var indexer = new object[0];
    PropertyInfo[] properties = myObjectType.GetProperties();
    foreach (var info in properties)
    {
        var value = info.GetValue(item, indexer);
        dict.Add(info.Name, value);
    }
    return dict;
}
Andrew Orsich
quelle
Dies unterstützt keine Verschachtelung.
Cesar
10

Ich kann den Castle DictionaryAdapter nur empfehlen , eines der bestgehüteten Geheimnisse dieses Projekts. Sie müssen nur eine Schnittstelle mit den gewünschten Eigenschaften definieren, und in einer Codezeile generiert der Adapter eine Implementierung, instanziiert sie und synchronisiert ihre Werte mit einem Wörterbuch, das Sie übergeben. Ich verwende es, um meine AppSettings stark einzugeben ein Webprojekt:

var appSettings =
  new DictionaryAdapterFactory().GetAdapter<IAppSettings>(ConfigurationManager.AppSettings);

Beachten Sie, dass ich keine Klasse erstellen musste, die IAppSettings implementiert - der Adapter erledigt dies im laufenden Betrieb. Auch wenn ich in diesem Fall nur lese, würde der Adapter theoretisch das zugrunde liegende Wörterbuch mit diesen Änderungen synchron halten, wenn ich Eigenschaftswerte für appSettings festlegen würde.

Todd Menier
quelle
2
Ich denke, das "bestgehütete Geheimnis" ist eines einsamen Todes gestorben. Der Link Castle DictionaryAdapter oben sowie die Download-Links für "DictionaryAdapter" auf Castleproject.org gehen alle auf eine 404-Seite :(
Shiva
1
Nein! Es ist immer noch im Schloss. Ich habe den Link behoben.
Gaspar Nagy
Castle war eine großartige Bibliothek, die irgendwie übersehen wurde
bikeman868
5

Durch Reflexion können Sie von einem Objekt zu einem Wörterbuch gelangen, indem Sie die Eigenschaften durchlaufen.

Um in die andere Richtung zu gehen, müssen Sie in C # ein dynamisches ExpandoObject verwenden (das tatsächlich bereits von IDictionary erbt und dies für Sie getan hat), es sei denn, Sie können den Typ aus der Sammlung von Einträgen in der Liste ableiten Wörterbuch irgendwie.

Wenn Sie sich in .NET 4.0 befinden, verwenden Sie ein ExpandoObject, da Sie sonst noch viel zu tun haben ...

Massiv
quelle
4

Ich denke, Sie sollten Reflexion verwenden. Etwas wie das:

private T ConvertDictionaryTo<T>(IDictionary<string, object> dictionary) where T : new()
{
    Type type = typeof (T);
    T ret = new T();

    foreach (var keyValue in dictionary)
    {
        type.GetProperty(keyValue.Key).SetValue(ret, keyValue.Value, null);
    }

    return ret;
}

Es nimmt Ihr Wörterbuch und durchläuft es und legt die Werte fest. Sie sollten es besser machen, aber es ist ein Anfang. Sie sollten es so nennen:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);
TurBas
quelle
Für was möchten Sie einen Aktivator verwenden, wenn Sie die "neue" generische Einschränkung verwenden können? :)
Matías Fidemraizer
@ Matías Fidemraizer Du hast absolut recht. Mein Fehler. Änderte es.
TurBas
Vielen Dank ur awesome, ein Problem, wenn eine Klasse nicht einige der Eigenschaften hat, die im Wörterbuch sind. Es sollte nur mit den in einer Klasse vorhandenen Eigenschaften übereinstimmen und andere überspringen. Ab sofort habe ich versucht, eine bessere Option dafür zu finden.
Sunil Chaudhary
3
public class SimpleObjectDictionaryMapper<TObject>
{
    public static TObject GetObject(IDictionary<string, object> d)
    {
        PropertyInfo[] props = typeof(TObject).GetProperties();
        TObject res = Activator.CreateInstance<TObject>();
        for (int i = 0; i < props.Length; i++)
        {
            if (props[i].CanWrite && d.ContainsKey(props[i].Name))
            {
                props[i].SetValue(res, d[props[i].Name], null);
            }
        }
        return res;
    }

    public static IDictionary<string, object> GetDictionary(TObject o)
    {
        IDictionary<string, object> res = new Dictionary<string, object>();
        PropertyInfo[] props = typeof(TObject).GetProperties();
        for (int i = 0; i < props.Length; i++)
        {
            if (props[i].CanRead)
            {
                res.Add(props[i].Name, props[i].GetValue(o, null));
            }
        }
        return res;
    }
}
Sawan
quelle
2

Aufbauend auf der Antwort von Matías Fidemraizer finden Sie hier eine Version, die das Binden an andere Objekteigenschaften als Zeichenfolgen unterstützt.

using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace WebOpsApi.Shared.Helpers
{
    public static class MappingExtension
    {
        public static T ToObject<T>(this IDictionary<string, object> source)
            where T : class, new()
        {
            var someObject = new T();
            var someObjectType = someObject.GetType();

            foreach (var item in source)
            {
                var key = char.ToUpper(item.Key[0]) + item.Key.Substring(1);
                var targetProperty = someObjectType.GetProperty(key);


                if (targetProperty.PropertyType == typeof (string))
                {
                    targetProperty.SetValue(someObject, item.Value);
                }
                else
                {

                    var parseMethod = targetProperty.PropertyType.GetMethod("TryParse",
                        BindingFlags.Public | BindingFlags.Static, null,
                        new[] {typeof (string), targetProperty.PropertyType.MakeByRefType()}, null);

                    if (parseMethod != null)
                    {
                        var parameters = new[] { item.Value, null };
                        var success = (bool)parseMethod.Invoke(null, parameters);
                        if (success)
                        {
                            targetProperty.SetValue(someObject, parameters[1]);
                        }

                    }
                }
            }

            return someObject;
        }

        public static IDictionary<string, object> AsDictionary(this object source, BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)
        {
            return source.GetType().GetProperties(bindingAttr).ToDictionary
            (
                propInfo => propInfo.Name,
                propInfo => propInfo.GetValue(source, null)
            );
        }
    }
}
Daniel Lewis
quelle
1

Wenn Sie Asp.Net MVC verwenden, sehen Sie sich Folgendes an:

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

Dies ist eine statische öffentliche Methode für die System.Web.Mvc.HtmlHelper-Klasse.

magritte
quelle
Ich werde es nicht verwenden, da es "_" durch "-" ersetzt. Und du brauchst MVC. Verwenden Sie die reine Routing-Klasse: new RouteValueDictionary (objectHere);
regisbsb
0
    public Dictionary<string, object> ToDictionary<T>(string key, T value)
    {
        try
        {
            var payload = new Dictionary<string, object>
            {
                { key, value }
            }; 
        } catch (Exception e)
        {
            return null;
        }
    }

    public T FromDictionary<T>(Dictionary<string, object> payload, string key)
    {
        try
        {
            JObject jObject = (JObject) payload[key];
            T t = jObject.ToObject<T>();
            return (t);
        }
        catch(Exception e) {
            return default(T);
        }
    }
Williams Abiola
quelle
Während dieser Code die Frage möglicherweise beantwortet, verbessert die Bereitstellung eines zusätzlichen Kontexts darüber, warum und / oder wie dieser Code die Frage beantwortet, ihren langfristigen Wert.
Baikho