2016-07-09 144 views
0

我正在處理項目以通用方式篩選列表。我在運行時檢索到IEnumerable<T>,但我不知道T是什麼。我需要將我檢索的列表轉換爲IEnumerable<T>而不是IEnumerable,因爲我需要擴展方法,如ToListWhere。這是我的代碼。如何將`IEnumerable <Unknown T>`轉換爲`IEnumerable <Whatever>`

private IList<object> UpdateList(KeyValuePair<string, string>[] conditions) 
{ 
    // Here I want to get the list property to update 
    // The List is of List<Model1>, but of course I don't know that at runtime 
    // casting it to IEnumerable<object> would give Invalid Cast Exception 
    var listToModify = (IEnumerable<object>)propertyListInfoToUpdate.GetValue(model); 

    foreach (var condition in conditions) 
    { 
     // Filter is an extension method defined below 
     listToModify = listToModify.Filter(condition .Key, condition .Value); 
    } 

    // ToList can only be invoked on IEnumerable<T> 
    return listToModify.ToList(); 
} 



public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, string propertyName, object value) 
{ 
    var parameter = Expression.Parameter(typeof(T), "x"); 
    var property = Expression.Property(parameter, propertyName); 
    var propertyType = ((PropertyInfo)property.Member).PropertyType; 
    Expression constant = Expression.Constant(value); 

    if (((ConstantExpression)constant).Type != propertyType) 
    { constant = Expression.Convert(constant, propertyType); } 

    var equality = Expression.Equal(property, constant); 
    var predicate = Expression.Lambda<Func<T, bool>>(equality, parameter); 

    var compiled = predicate.Compile(); 

    // Where can only be invoked on IEnumerable<T> 
    return source.Where(compiled); 
} 

同時請注意,我不能檢索這樣

((IEnumerable)propertyListInfoToUpdate.GetValue(model)).Cast<object>() 

名單,因爲它會產生在Filter extention

ParameterExpression of type 'Model1' cannot be used for delegate parameter of type 'System.Object' 
+1

這對我來說看起來像一個非常合理的問題,而不是那種肯定應該被低估而沒有任何評論的那種。 –

+0

爲什麼不給'UpdateList'一個泛型類型參數? 'UpdateList '。並輸入'propertyListInfoToUpdate.GetValue(model)'作爲參數(無論如何,這是更好的方法,因爲現在你的方法依賴於外部狀態)。 –

+0

@GertArnold同樣的問題仍然存在,因爲外部調用者仍然不知道什麼是「T」。我所知道的只是我需要檢索的財產,沒有更多。 – Ayman

回答

1

使用GetGenericArgumentsMakeGenericMethod以下例外接口通用簽名。

private IList<object> UpdateList(KeyValuePair<string, string> conditions) 
{ 
    var rawList = (IEnumerable)propertyListInfoToUpdate.GetValue(model); 

    var listItemType = propertyListInfoToUpdate.PropertyType.GetGenericArguments().First(); 
    var filterMethod = this.GetType().GetMethod("Filter").MakeGenericMethod(genericType); 

    object listToModify = rawList; 
    foreach (var condition in conditions) 
    { 
     listToModify = filterMethod.Invoke(null, new object[] { listToModify, condition.Key, condition.Value }) 
    } 

    return ((IEnumerable)listToModify).Cast<object>().ToList(); 
} 

假設你propertyListInfoToUpdatePropertyInfo和物業類型爲List<T>

1

爲什麼你根本用Expression?沒有好的Minimal, Complete, and Verifiable code example很難理解你的問題。即使我們可以解決演員問題,你仍然會返回IList<object>。這不像代碼的消費者會受益於鑄造。

解決鑄造問題是不太可能的,至少不是你想要的方式。另一種方法是動態調用Filter()。在過去的日子裏,我們不得不通過反射來做到這一點,但dynamic類型爲我們提供了運行時支持。你可以得到它的工作是這樣的:

private IList<object> UpdateList(KeyValuePair<string, string>[] conditions) 
    { 
     dynamic listToModify = propertyListInfoToUpdate.GetValue(model); 

     foreach (var condition in conditions) 
     { 
      // Filter is an extension method defined below 
      listToModify = Filter(listToModify, condition.Key, condition.Value); 
     } 

     // ToList can only be invoked on IEnumerable<T> 
     return ((IEnumerable<object>)Enumerable.Cast<object>(listToModify)).ToList(); 
    } 

注意:你的原始代碼是無效的;我假定conditions應該是一個數組,但當然,如果將其更改爲任何具有GetEnumerator()方法的東西,那就沒問題。所有這一切說,在我看來,由於缺乏編譯時類型參數,只更改Filter()方法以使其不是通用的,因此您可以使用object.Equals()來比較屬性值爲條件值。您似乎正在跳過大量的使用泛型的箍,而沒有獲得泛型的編譯時好處。

請注意,如果所有這些都是執行LINQ查詢方法,那麼只需使用Enumerable.Cast<object>()並直接使用object.Equals()即可輕鬆解決。這是事實,你想使用表達式來訪問屬性值(一個合理的目標),這是複雜的問題。但即使在那裏,你仍然可以堅持IEnumerable<object>,並將object.Equals()建立在你的表達中。

+0

感謝您的回覆彼得。 1.拋開問題的寫法,我返回'IList ',因爲在我的邏輯中,我返回的內容並不重要,甚至可能只返回'IList'或'IEnumerable',但這不是重點。 2.感謝「動態」提示。 3.我的數組輸入錯誤。 4.我需要'Filter'擴展名的泛型能夠構造所需的表達式並且無需反射就可以調用'Where'擴展方法。另外我已經提到''Cast '拋出運行時異常。 – Ayman

1

每次創建表達式並編譯它都非常昂貴。您應該直接使用Reflection或像FastMember這樣的庫(或緩存表達式)。此外,您的代碼使用Expression.Equal,它轉換爲相等運算符(==),這不是比較對象的好方法。你應該使用Object.Equals

下面是使用FastMember代碼:

private IList<object> UpdateList(KeyValuePair<string, string>[] conditions) 
{ 
    var listToModify = ((IEnumerable)propertyListInfoToUpdate.GetValue(model)).Cast<object>(); 

    foreach (var condition in conditions) 
    { 
     listToModify = listToModify.Where(o => Equals(ObjectAccessor.Create(o)[condition.Key], condition.Value)); 
    } 

    return listToModify.ToList(); 
} 

附註 - 您Filter方法並不真正需要的仿製藥。可以通過調整表達式來接受並返回IEnumerable<object>,這也可以解決您的問題。

相關問題