2016-07-09 14 views
0

werfe Ich arbeite an einem Projekt, um eine Liste in einer allgemeinen Weise zu filtern. Ich erhalte eine IEnumerable<T> zur Laufzeit, aber ich weiß nicht, was T ist. Ich muss die Liste, die ich abrufe, in IEnumerable<T> und nicht in IEnumerable umwandeln, da ich Erweiterungsmethoden wie ToList und Where benötige. Hier ist mein Code.Wie man ein `IEnumerable <Unknown T>` auf `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); 
} 

Bitte beachten Sie auch, dass ich die Liste wie dies

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

abrufen kann, da es

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

Das sieht wie eine sehr vernünftige Frage für mich aus, nicht die Art, die sicherlich ohne Kommentar heruntergestimmt werden sollte. –

+0

Warum geben Sie 'UpdateList' keinen generischen Typparameter? 'UpdateList '. Und geben Sie 'propertyListInfoToUpdate.GetValue (model)' als Parameter ein (was sowieso besser ist, da Ihre Methode jetzt vom externen Status abhängig ist). –

+0

@GertArnold Das gleiche Problem wird immer noch da sein, weil der externe Anrufer immer noch nicht weiß, was 'T' ist. Alles, was ich weiß, ist die Eigenschaft, die ich abrufen muss, nichts mehr. – Ayman

Antwort

1

Verwenden unten Ausnahme in der Filter Erweiterung generiert GetGenericArguments und MakeGenericMethod generische Schnittstelle Signaturen.

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

Ihre Vorausgesetzt, dass propertyListInfoToUpdate ist ein PropertyInfo und der Objekttyp ist List<T>.

1

Warum verwenden Sie überhaupt Expression? Es ist schwierig, Ihre Frage ohne eine gute Minimal, Complete, and Verifiable code example zu verstehen. Selbst wenn wir die Casting-Frage lösen könnten, geben Sie immer noch IList<object> zurück. Es ist nicht so, dass der Verbraucher des Codes vom Casting profitiert.

Und es ist nicht wirklich möglich, das Casting-Problem zu lösen, zumindest nicht in der Art, wie Sie scheinen wollen. Ein anderer Ansatz wäre, die Filter() dynamisch aufzurufen. In den alten Tagen mussten wir dies mithilfe von Reflektion tun, aber der Typ dynamic gibt uns Laufzeitunterstützung. Sie könnten es so etwas wie funktionieren:

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

HINWEIS: Ihr ursprünglicher Code ist nicht gültig; Ich habe die Annahme gemacht, dass conditions ein Array sein soll, aber natürlich, wenn Sie es zu etwas ändern, das eine GetEnumerator() Methode hat, wäre das in Ordnung.

Alles, was gesagt, scheint es mir, dass das Fehlen eines Kompilierung-Typ-Parameter gegeben, wäre es direkt sein nur Ihre Filter() Methode zu ändern, so dass es nicht generisch, und so, dass Sie object.Equals() verwenden die zum Vergleich Eigenschaftswert für den Zustandswert. Sie scheinen durch viele Reifen zu springen, um Generika zu verwenden, ohne den Kompilierzeitvorteil von Generika zu erhalten.

Beachten Sie, dass, wenn dies alles zum Ausführen von LINQ-Abfragemethoden war, dies einfach mit Enumerable.Cast<object>() und object.Equals() direkt adressiert werden konnte. Es ist die Tatsache, dass Sie Ausdrücke verwenden möchten, um auf den Eigenschaftswert (ein vernünftiges Ziel) zuzugreifen, der das Problem erschwert. Aber selbst dort können Sie mit IEnumerable<object> bleiben und einfach die object.Equals() in Ihren Ausdruck einbauen.

+0

Danke für Ihre Antwort Peter. 1. Wenn ich beiseite lege, wie die Frage geschrieben wurde, gebe ich eine "IList " zurück, weil es in meiner Logik nicht wichtig ist, was ich zurückgebe, ich könnte sogar "IList" oder "IEnumerable" zurückgeben, aber das ist nicht der Punkt. 2. Danke für den "dynamischen" Tipp. 3. mein schlechtes auf dem Array Tippfehler. 4. Ich brauche die generische Erweiterung 'Filter', um den benötigten Ausdruck konstruieren und die' Where'-Erweiterungsmethode ohne Reflektion aufrufen zu können. Auch habe ich bereits erwähnt, dass 'Cast ' Laufzeitausnahme wirft. – Ayman

1

Einen Ausdruck zu erstellen und jedes Mal zu kompilieren ist sehr teuer. Sie sollten entweder Reflection direkt oder eine Bibliothek wie FastMember verwenden (oder die Ausdrücke zwischenspeichern). Darüber hinaus verwendet Ihr Code Expression.Equal, was in den Gleichheitsoperator (==) übersetzt wird. Dies ist keine gute Methode zum Vergleichen von Objekten. Sie sollten Object.Equals verwenden.

Hier ist der Code FastMember mit:

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

Randnotiz - Ihre Filter Methode wirklich braucht keine Generika. Es kann geändert werden, um eine IEnumerable<object> zu akzeptieren und zurückzugeben, indem Sie den Ausdruck optimieren, der auch Ihr Problem gelöst hätte.

Verwandte Themen