2017-03-16 31 views
0

我試圖做一個泛型類,它能夠更新對象的任何屬性。通用對象更新器 - 將數組轉換爲列表

我使它適用於多種情況,例如,單值屬性(int,string,bool等)。如果屬性爲IEnumerable<T>,那麼它也可以很好地工作,在這種情況下,列表或數組可以正常工作。

但是如果屬性爲List<T>,那麼我會碰壁,但要分配的值是Array(或其他方式)。在「標準」情況下,我可以簡單地使用ToList()ToArray,但在一般情況下,我很困惑如何。

下面的代碼

public static class ObjectUpdater 
{ 
    public static T Patch<T>(T obj, IEnumerable<KeyValuePair<string, object>> delta) 
    { 
     if (obj == null || delta == null) 
     { 
      return obj; 
     } 
     foreach (var deltaItem in delta) 
     { 
      Patch(obj, deltaItem.Key, deltaItem.Value); 
     } 
     return obj; 
    } 

    private static void Patch<T>(T obj, string propertyName, object value) 
    { 
     var propertyInfo = obj.GetType().GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); 
     if (propertyInfo == null || !propertyInfo.CanRead || !propertyInfo.CanWrite) 
     { 
      throw new ArgumentException($"Property '{propertyName}' doesn't exist or cannot be updated"); 
     } 
     SetPropertyValue(obj, value, propertyInfo); 
    } 

    private static void SetPropertyValue(object obj, object value, PropertyInfo propertyInfo) 
    { 
     if (propertyInfo.PropertyType.IsEnum) 
     { 
      propertyInfo.SetValue(obj, Convert.ToInt32(value)); 
      return; 
     } 

     // property parsing is based on the target property's TryParse() method 
     // big/small enough float/DateTime values had issues, as the ToString() might lose precision >> they're handled separately 
     if (value is float) 
     { 
      SetPropertyValueAsFloat(obj, (float)value, propertyInfo); 
      return; 
     } 

     if (value is DateTime) 
     { 
      SetPropertyValueAsDateTime(obj, (DateTime)value, propertyInfo); 
      return; 
     } 

     var systemType = propertyInfo.PropertyType.UnderlyingSystemType; 
     var tryParse = systemType.GetMethod("TryParse", new[] {typeof(string), systemType.MakeByRefType()}); 
     if (tryParse == null) 
     { 
      propertyInfo.SetValue(obj, value); 
      return; 
     } 
     var parameters = new object[] 
         { 
          value.ToString(), 
          null 
         }; 
     var canParse = (bool) tryParse.Invoke(null, parameters); 
     propertyInfo.SetValue(obj, canParse ? parameters[1] : value); 
    } 

    private static void SetPropertyValueAsDateTime(object obj, DateTime value, PropertyInfo propertyInfo) 
    { 
     // code to handle DateTime value 
    } 
} 

// and two test methods 
[Fact] 
private void Patch_StringListPropertyFromArrayValue() 
{ 
    var sourceObject = new TestClassWithCollectionProperties 
         { 
          StringListProperty = null 
         }; 
    var expectedResult = new TestClassWithCollectionProperties 
         { 
          StringListProperty = new List<string> 
                { 
                 "abc", 
                 "def" 
                } 
         }; 
    var delta = new List<KeyValuePair<string, object>> 
       { 
        new KeyValuePair<string, object>("StringListProperty", 
         new [] 
         { 
          "abc", 
          "def" 
         }) 
       }; 

    var result = ObjectUpdater.Patch(sourceObject, delta); 

    result.ShouldBeEquivalentTo(expectedResult); 
} 

[Fact] 
private void Patch_StringArrayPropertyFromListValue() 
{ 
    var sourceObject = new TestClassWithCollectionProperties 
         { 
          StringArrayProperty = null 
         }; 
    var expectedResult = new TestClassWithCollectionProperties 
         { 
          StringArrayProperty = new[] 
                { 
                 "abc", 
                 "def" 
                } 
         }; 
    var delta = new List<KeyValuePair<string, object>> 
       { 
        new KeyValuePair<string, object>("StringArrayProperty", 
         new List<string> 
         { 
          "abc", 
          "def" 
         }) 
       }; 

    var result = ObjectUpdater.Patch(sourceObject, delta); 

    result.ShouldBeEquivalentTo(expectedResult); 
} 

但該試驗作爲ObjectUpdater.Patch拋出一個System.ArgumentException以下消息類型的

對象「System.String []」不能被轉換爲類型失敗「系統.Collections.Generic.List`1 [System.String]」。

有什麼建議嗎?

+0

嗯..你可以試試你的清單,'.ToArray轉換成數組()'做你的東西,並將其轉換回循環內的'List'也許? – uTeisT

回答

1

那麼,你可以前tryParse嘗試像這樣的東西在某處的代碼處理的具體情況:上述

if (value != null && value.GetType() != propertyInfo.PropertyType 
    // from T[] 
    && value.GetType().IsArray && value.GetType().GetArrayRank() == 1 
    // to List<T> 
    && propertyInfo.PropertyType.IsGenericType 
    && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>) 
    && propertyInfo.PropertyType.GetGenericArguments()[0] == value.GetType().GetElementType()) 
{ 
    var T = value.GetType().GetElementType(); 
    var listT = typeof(List<>).MakeGenericType(T); 
    // new List<T>(IEnumerable<T> items) 
    var enumerableT = typeof(IEnumerable<>).MakeGenericType(T); 
    var newListT = listT.GetConstructor(new Type [] { enumerableT }); 
    var list = newListT.Invoke(new[] { value }); 
    propertyInfo.SetValue(obj, list); 
    return; 
} 

處理分配形式T[]List<T>。爲了處理相反的分配,通過反射增加一個類似if條件與類型互換,簡單地調用List<T>.ToArray)方法:

if (value != null && value.GetType() != propertyInfo.PropertyType 
    // to T[] 
    && propertyInfo.PropertyType.IsArray && propertyInfo.PropertyType.GetArrayRank() == 1 
    // from List<T> 
    && value.GetType().IsGenericType 
    && value.GetType().GetGenericTypeDefinition() == typeof(List<>) 
    && value.GetType().GetGenericArguments()[0] == propertyInfo.PropertyType.GetElementType()) 
{ 
    // List<T>.ToArray() 
    var toArray = value.GetType().GetMethod("ToArray", Type.EmptyTypes); 
    var array = toArray.Invoke(value, null); 
    propertyInfo.SetValue(obj, array); 
    return; 
} 
+0

是的,它確實處理陣列>>列表案例。你是否也可以提供List >> Array方案的版本? – Szeki

+0

當然。您只需要檢測該案例並通過反射調用'List .ToArray'方法。但請注意,你正在進入無限循環:) –

+0

無論如何,你走了。 –