2016-07-09 12 views
0

私は一般的な方法でリストをフィルタリングするプロジェクトに取り組んでいます。実行時にIEnumerable<T>を取得していますが、Tが何であるかわかりません。 ToListWhereのような拡張メソッドが必要なので、私が検索しているリストをIEnumerable<T>にキャストし、IEnumerableをキャストする必要はありません。ここに私のコードです。`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); 
} 

また、それは一般的なインタフェースするFilter拡張子

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に以下の例外が発生しますので、私はこの

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

のようなリストを取得することはできませんのでご了承ください署名。あなたのpropertyListInfoToUpdateを想定し

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

PropertyInfoで、プロパティの型が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.必要な式を構築し、 'Where'拡張メソッドをリフレクションせずに呼び出せるように、' Filter'拡張モジュールにジェネリックが必要です。また、私はすでに 'キャスト'が実行時例外をスローすると述べました。 – 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>を受け入れて返すように変更することもできますが、これもあなたの問題を解決しました。

関連する問題