オブジェクトを辞書に、またはその逆にマッピングします。


90

オブジェクトを辞書に、またはその逆にマップするためのエレガントで迅速な方法はありますか?

例:

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

になります

SomeClass b = new SomeClass();
b.Id=1;
b.Name="Ahmad";
// ..........

最速の方法は、protobufのようなコード生成によるものです...私は式ツリーを使用して、反射をできるだけ頻繁に回避しました
Konrad

以下のすべての自己コード化された回答は、深い変換をサポートしておらず、実際のアプリケーションでは機能しません。Earnerplatesが提案するNewtonsoftのようなライブラリを使用する必要があります
Cesar

回答:


175

2つの拡張メソッドでいくつかのリフレクションとジェネリックを使用すると、それを実現できます。

そうです、他の人はほとんど同じ解決策をしましたが、これはより少ない反射を使用し、よりパフォーマンス的にそしてはるかに読みやすくなります:

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

私は実装が好きで、実際に使い始めましたが、パフォーマンスに関するフィードバックはありますか?パフォーマンスに関しては、リフレクションが最大ではないことを私は知っていますか?コメントはありますか?
nolimit 2013

@nolimit実際のパフォーマンスはわかりませんが、それほど悪くはないと思います(もちろん、リフレクションを回避するよりも最悪です...)。実際、代替手段は何ですか?プロジェクトの要件によっては、リフレクションよりもはるかに高速な動的型付けを使用するなど、他のオプションがあるかもしれません...誰が知っていますか?:)
マティアスFidemraizer

私の場合、代替手段は割り当てによってオブジェクトを作成するのと同じくらい簡単ですが、オブジェクトを作成するためのきちんとした安価な方法を探しています。そうは言っても、私はこれを見つけました。これは、そのコードを使用するのにそれほど費用がかからないことを意味します。
nolimit 2013

@nolimitええ、問題ないようです。必要な問題に適切なツールを使用するだけです:Dあなたの場合、それは魅力のように機能するはずです。私はまだコードで何かを調整できると信じています!
マティアスFidemraizer

@nolimit最初のメソッドでプロパティごとにGetType()呼び出されなくなったことを確認しますsomeObject
マティアスFidemraizer

30

Newtonsoftを使用して、最初に辞書をJSON文字列に変換します。

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

次に、JSON文字列をオブジェクトに逆シリアル化します

var myobject = JsonConvert.DeserializeObject<AOCAdvancedSettings>(json);

1
シンプルでクリエイティブ!
kamalpreet 2018

1
このアプローチは注意して使用してください。多数(数百)の辞書に使用すると、アプリケーションが機能しなくなります。
amackay 1119

12

リフレクションはここでのみ役立つようです。オブジェクトを辞書に、またはその逆に変換する小さな例を実行しました。

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

これはネストをサポートしていません。
セザール

10

Castle DictionaryAdapterを強くお勧めします。これは、そのプロジェクトで最もよく守られている秘密の1つです。必要なプロパティを使用してインターフェイスを定義するだけで、1行のコードで、アダプターが実装を生成してインスタンス化し、渡した辞書と値を同期します。これを使用して、AppSettingsを厳密に入力します。 Webプロジェクト:

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

IAppSettingsを実装するクラスを作成する必要がなかったことに注意してください-アダプターはその場でそれを行います。また、この場合は読んでいるだけですが、理論的には、appSettingsでプロパティ値を設定している場合、アダプターは基になるディクショナリをそれらの変更と同期させます。


2
「最高の秘密」は孤独死したと思います。上記のCastleDictionaryAdapterリンク、およびcastleproject.orgの「DictionaryAdapter」のダウンロードリンクはすべて404ページに移動します:(
Shiva

1
番号!それはまだCastle.Coreにあります。リンクを修正しました。
ガスパルナジ

城は、GOTが何とか見落としていることの偉大なライブラリだった
bikeman868

5

リフレクションは、プロパティを反復処理することにより、オブジェクトからディクショナリに移動できます。

逆にするには、動的なExpandoObjectを使用する必要があります、C#で(実際にはすでにIDictionaryから継承しているため、これを行っています)を使用する必要があります。ただし、どういうわけか辞書。

したがって、.NET 4.0の土地を使用している場合は、ExpandoObjectを使用してください。そうでない場合は、多くの作業が必要になります...


4

リフレクションを使うべきだと思います。このようなもの:

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

辞書を取得してループし、値を設定します。あなたはそれをより良くするべきですが、それは始まりです。あなたはそれをこのように呼ぶべきです:

SomeClass someClass = ConvertDictionaryTo<SomeClass>(a);

「新しい」ジェネリック制約を使用できる場合、アクティベーターを使用したいものは何ですか?:)
マティアスFidemraizer

@MatíasFidemraizerあなたは絶対に正しいです。私の間違い。変更しました。
TurBas 2011

素晴らしいありがとう、someclassに辞書にあるプロパティのいくつかがない場合の1つの問題。一部のクラスに存在するプロパティのみに一致し、他のプロパティはスキップする必要があります。現在、これに適したオプションをキャッチしてみてください。
SunilChaudhary18年

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

2

MatíasFidemraizerの回答に基づいて、文字列以外のオブジェクトプロパティへのバインドをサポートするバージョンを次に示します。

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

1

Asp.Net MVCを使用している場合は、以下を参照してください。

public static RouteValueDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes);

これは、System.Web.Mvc.HtmlHelperクラスの静的パブリックメソッドです。


「_」を「-」に置き換えるので使用しません。そして、MVCが必要です。純粋なルーティングクラスを使用します:new RouteValueDictionary(objectHere);
regisbsb 2014

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

このコードは質問に答えることができますが、このコードが質問に答える理由や方法に関する追加のコンテキストを提供すると、長期的な価値が向上します。
baikho 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.