2013-10-22 14 views
6

私のアプリケーションでは、実行時にプロパティを作成/削除するためにExpandoObjectを使用する必要があります。しかし、関数の返されたExpandoObjectを対応するオブジェクト/クラスにマップする必要があります。仮定としてExpandoObjectを再帰的にマッピングする

  1. それは再帰的ExpandoObject の内側のオブジェクトをマップしません。だから私は仕事をしていませんが、3つの問題に小さなマッパーを思い付いています。
  2. intをNullableにマップしようとすると、単に検出して正しくキャストする方法が見つからないため、タイプが の不一致になります。
  3. フィールドをマッピングできませんpublic string Property;

コード:

I-実装:

public static class Mapper<T> where T : class 
{ 
    #region Properties 

    private static readonly Dictionary<string, PropertyInfo> PropertyMap; 

    #endregion 

    #region Ctor 

    static Mapper() { PropertyMap = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToDictionary(p => p.Name.ToLower(), p => p); } 

    #endregion 

    #region Methods 

    public static void Map(ExpandoObject source, T destination) 
    { 
     if (source == null) 
      throw new ArgumentNullException("source"); 
     if (destination == null) 
      throw new ArgumentNullException("destination"); 

     foreach (var kv in source) 
     { 
      PropertyInfo p; 
      if (PropertyMap.TryGetValue(kv.Key.ToLower(), out p)) 
      { 
       Type propType = p.PropertyType; 
       if (kv.Value == null) 
       { 
        if (!propType.IsByRef && propType.Name != "Nullable`1") 
        { 
         throw new ArgumentException("not nullable"); 
        } 
       } 
       else if (kv.Value.GetType() != propType) 
       { 
        throw new ArgumentException("type mismatch"); 
       } 
       p.SetValue(destination, kv.Value, null); 
      } 
     } 
    } 

    #endregion 
} 

II:用途:

public static void Main() 
{ 
    Class c = new Class(); 
    dynamic o = new ExpandoObject(); 
    o.Name = "Carl"; 
    o.Level = 7; 
    o.Inner = new InnerClass 
       { 
         Name = "Inner Carl", 
         Level = 10 
       }; 

    Mapper<Class>.Map(o, c); 

    Console.Read(); 
} 

internal class Class 
{ 
    public string Name { get; set; } 
    public int? Level { get; set; } 
    public InnerClass Inner { get; set; } 
    public string Property; 
} 

internal class InnerClass 
{ 
    public string Name { get; set; } 
    public int? Level { get; set; } 
} 
+0

どれでも答えを... –

答えて

4

、3-プロパティーがpublic string Property;のようにフォーマットされている場合、getプロパティはそれを取得しません。

ああ、これはプロパティではなく、フィールドです。あなたもフィールドを考慮する必要がある場合。

static Mapper() 
{ 
    PropertyMap = typeof(T).GetProperties(BindingFlags.Public | 
               BindingFlags.NonPublic | 
               BindingFlags.Instance) 
               .ToDictionary(p => p.Name.ToLower(), p => p); 

    FieldMap = typeof(T).GetFields(BindingFlags.Public | 
               BindingFlags.NonPublic | 
               BindingFlags.Instance) 
               .ToDictionary(f => f.Name.ToLower(), f => f); 
} 

2 - 私はそれを正しく検出し、キャストする方法を見つけることができないので、単にそれが型の不一致をスローしますint型のNullableにマップしようとします。

Nullableタイプをチェックする理由は、反射を理解してください。値が有効な場合は、割り当てられます。

public static void Map(ExpandoObject source, T destination) 
{ 
    if (source == null) 
     throw new ArgumentNullException("source"); 
    if (destination == null) 
     throw new ArgumentNullException("destination"); 

    foreach (var kv in source) 
    { 
     PropertyInfo p; 
     if (PropertyMap.TryGetValue(kv.Key.ToLower(), out p)) 
     { 
      p.SetValue(destination, kv.Value, null); 
     } 
     else 
     { 
      FieldInfo f; 
      if (FieldMap.TryGetValue(kv.Key.ToLower(), out f)) 
      { 
       f.SetValue(destination, kv.Value); 
      } 
     } 
    } 
} 

1 - はずように、それは再帰的にExpandoObjectの内側のオブジェクトをマップしません。

InnerClass以上で動作するようです。

Class c = new Class(); 
dynamic o = new ExpandoObject(); 
o.Name = "Carl"; 
o.Level = 7; 
o.Inner = new InnerClass 
{ 
    Name = "Inner Carl", 
    Level = 10 
}; 

o.Property = "my Property value"; // dont forget to set this 

Mapper<Class>.Map(o, c); 

EDIT:あなたのコメントに基づいて、私は2つのオーバーロードされたメソッドMergePropertyを作成しました。同様にオーバーロードされたフィールドのメソッドを記述できます。

public static void MergeProperty(PropertyInfo pi, ExpandoObject source, object target) 
{ 
    Type propType = pi.PropertyType; 

    // dont recurse for value type, Nullable<T> and strings 
    if (propType.IsValueType || propType == typeof(string)) 
    { 
     var sourceVal = source.First(kvp => kvp.Key == pi.Name).Value; 
     if(sourceVal != null) 
      pi.SetValue(target, sourceVal, null); 
    } 
    else // recursively map inner class properties 
    { 
     var props = propType.GetProperties(BindingFlags.Public | 
                BindingFlags.NonPublic | 
                BindingFlags.Instance); 

     foreach (var p in props) 
     { 
      var sourcePropValue = source.First(kvp => kvp.Key == pi.Name).Value; 
      var targetPropValue = pi.GetValue(target, null); 

      if (sourcePropValue != null) 
      { 
       if (targetPropValue == null) // replace 
       { 
        pi.SetValue(target, source.First(kvp => kvp.Key == pi.Name).Value, null); 
       } 
       else 
       { 
        MergeProperty(p, sourcePropValue, targetPropValue); 
       } 
      } 
     } 

    } 
} 

public static void MergeProperty(PropertyInfo pi, object source, object target) 
{ 
    Type propType = pi.PropertyType; 
    PropertyInfo sourcePi = source.GetType().GetProperty(pi.Name); 

    // dont recurse for value type, Nullable<T> and strings 
    if (propType.IsValueType || propType == typeof(string)) 
    { 
     var sourceVal = sourcePi.GetValue(source, null); 
     if(sourceVal != null) 
      pi.SetValue(target, sourceVal, null); 
    } 
    else // recursively map inner class properties 
    { 
     var props = propType.GetProperties(BindingFlags.Public | 
                BindingFlags.NonPublic | 
                BindingFlags.Instance); 

     foreach (var p in props) 
     { 
      var sourcePropValue = sourcePi.GetValue(source, null); 
      var targetPropValue = pi.GetValue(target, null); 

      if (sourcePropValue != null) 
      { 
       if (targetPropValue == null) // replace 
       { 
        pi.SetValue(target, sourcePi.GetValue(source, null), null); 
       } 
       else 
       { 
        MergeProperty(p, sourcePropValue, targetPropValue); 
       } 
      } 
     } 

    } 
} 

あなたがメソッドをこのように使用することができます?

public static void Map(ExpandoObject source, T destination) 
{ 
    if (source == null) 
     throw new ArgumentNullException("source"); 
    if (destination == null) 
     throw new ArgumentNullException("destination"); 

    foreach (var kv in source) 
    { 
     PropertyInfo p; 
     if (PropertyMap.TryGetValue(kv.Key.ToLower(), out p)) 
     { 
      MergeProperty(p, source, destination); 
     } 
     else 
     { 
      // do similar merge for fields 
     } 
    } 
} 
+0

まあ素敵な詳細な答えは、しかし、それはInnerClassで動作しますが、それは、それはそれの地図ではない置き換えられます。私は別の言葉では(ソースからのヌル値を無視し、既存のヌル値をヌルで置き換えないで)非内部プロパティの残りの部分で行ったように値を更新(マップ)したいからです。また、3,2,1の代わりに答え1,2,3を配置することを検討してください。D –

+0

「それはマップしないで置き換えますか?クローンしたいですか? '非内部値 'はどのようにマップされ、置き換えられませんか?私は理解していなかった。 – YK1

+0

mmm、私が伝えたいことは何ですか。そのマッパーの仕事は、宛先オブジェクトのフィールド/プロパティを更新することです、そして、マッパーがソースでnullフィールド/プロパティを見つけた場合、それは、対応するフィールド/プロパティをヌルで置き換えるのではなく、それ以外は更新されます。私がここにも欠けているのは、フィールド/プロパティがより多くの内部プロパティを含んでいるかどうかをマッパーに見せたいということです。これは同じプロセスです(これは再帰的マッピングの意味です) –