객체를 사전에 매핑하거나 그 반대로 매핑


92

객체를 사전에 매핑하는 우아한 빠른 방법이 있습니까?

예:

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

답변:


177

두 가지 확장 방법에서 일부 리플렉션과 제네릭을 사용하면이를 달성 할 수 있습니다.

맞습니다, 다른 사람들은 대부분 동일한 솔루션을 수행했지만 이것은 성능면에서 더 읽기 쉽고 더 읽기 쉬운 반사를 덜 사용합니다.

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

@nolimit 실제로 실제 성능을 모르지만 그렇게 나쁘지는 않은 것 같습니다 (물론 반사를 피하는 것보다 최악입니다 ...). 사실, 대안은 무엇입니까? 프로젝트의 요구 사항에 따라 리플렉션보다 훨씬 빠른 동적 타이핑을 사용하는 것과 같은 다른 옵션이있을 수 있습니다. 누가 압니까! :)
Matías Fidemraizer 2013-02-27

제 경우에 대안은 할당으로 객체를 만드는 것만 큼 간단하지만 객체를 만드는 깔끔하면서도 저렴한 방법을 찾고 있습니다. 그렇게 말하면서, 코드를 사용하는 것이 너무 비싸지 않다는 것을 의미합니다.
nolimit

@nolimit 예, 문제가없는 것 같습니다. 필요한 문제에 적합한 도구를 사용하는 것입니다. D 귀하의 경우에는 매력처럼 작동해야합니다. 아직 코드에서 뭔가 조정할 수 있다고 믿습니다!
Matías Fidemraizer 2013-02-27

@nolimit 첫 번째 메서드에서 더 이상 각 속성 GetType()someObject대해 호출되지 않는지 확인합니다 .
Matías Fidemraizer 2013

32

Newtonsoft를 사용하여 먼저 사전을 JSON 문자열로 변환하십시오.

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

그런 다음 JSON 문자열을 객체로 역 직렬화합니다.

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

1
간단하고 창의적입니다!
kamalpreet

1
이 방법은주의해서 사용하십시오. 수백 개의 사전에 사용하면 응용 프로그램이 엉망이됩니다.
amackay11 19

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

이것은 중첩을 지원하지 않습니다.
Cesar

10

이 프로젝트에서 가장 잘 보존 된 비밀 중 하나 인 Castle DictionaryAdapter를 적극 추천합니다 . 원하는 속성으로 인터페이스를 정의하기 만하면됩니다. 한 줄의 코드에서 어댑터는 구현을 생성하고 인스턴스화하고 전달한 사전과 해당 값을 동기화합니다.이를 사용하여 내 AppSettings를 웹 프로젝트 :

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

IAppSettings를 구현하는 클래스를 만들 필요가 없었습니다. 어댑터가 즉시 수행합니다. 또한이 경우에는 읽기만하고 있지만 이론적으로 appSettings에서 속성 값을 설정하는 경우 어댑터는 기본 사전을 해당 변경 사항과 동기화 상태로 유지합니다.


2
나는 "가장 잘 간직 된 비밀"이 외로운 죽음으로 죽었다고 생각한다. 위의 Castle DictionaryAdapter 링크와 castleproject.org의 "DictionaryAdapter"에 대한 다운로드 링크는 모두 404 페이지로 이동합니다 :(
Shiva

1
아니! 그것은 여전히 ​​성 안에 있습니다. 핵심. 링크를 수정했습니다.
Gaspar Nagy

성 GOT 어떻게 든 간과하는 훌륭한 도서관이었다
bikeman868

5

리플렉션은 속성을 반복하여 객체에서 사전으로 이동할 수 있습니다.

다른 방법으로 이동하려면 C #에서 동적 ExpandoObject (실제로는 이미 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);

"새로운"일반 제약 조건을 사용할 수 있다면 활성화기를 사용하고 싶습니까? :)
Matías Fidemraizer

@ Matías Fidemraizer 당신이 절대적으로 옳습니다. 내 실수. 변경했습니다.
TurBas

감사합니다. someclass에 사전에있는 속성이없는 경우 한 가지 문제가 있습니다. 그것은 someclass에 존재하는 속성과 일치하고 다른 것을 건너 뛰어야합니다.
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;
    }
}

3

Matías Fidemraizer의 답변을 바탕으로 문자열 이외의 객체 속성에 대한 바인딩을 지원하는 버전이 있습니다.

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

1
이 코드가 질문에 답할 수 있지만,이 코드가 질문에 답하는 이유 및 / 또는 방법에 대한 추가 컨텍스트를 제공하면 장기적인 가치가 향상됩니다.
baikho
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.