2009-08-08 15 views
24

¿Hay alguna forma en C# .NET 2.0! combinar varios Predicates?Combinar múltiples predicciones

Digamos que tengo el siguiente código.

List<string> names = new List<string>(); 
names.Add("Jacob"); 
names.Add("Emma"); 
names.Add("Michael"); 
names.Add("Isabella"); 
names.Add("Ethan"); 
names.Add("Emily"); 

List<string> filteredNames = names.FindAll(StartsWithE); 

static bool StartsWithE(string s) 
{ 
    if (s.StartsWith("E")) 
    { 
     return true; 
    } 
    else 
    { 
     return false; 
    } 
} 

Esto me da:

Emma 
Ethan 
Emily 

Así que esto es algo bastante fresco, pero sé que quiero ser capaz de filtrar el uso de múltiples predicados.

por lo que quiero ser capaz de decir algo como esto:

List<string> filteredNames = names.FindAll(StartsWithE OR StartsWithI); 

el fin de obtener:

Emma 
Isabella 
Ethan 
Emily 

¿Cómo puedo lograr esto? Actualmente solo estoy filtrando la lista completa dos veces y combinando los resultados después. Pero desafortunadamente esto es bastante ineficiente y aún más importante, pierdo el orden de clasificación original, lo cual no es aceptable en mi situación.

También necesito poder iterar sobre cualquier cantidad de filtros/predicados, ya que puede haber bastante.

Una vez más, es necesario que haya una solución .NET 2.0 por desgracia no puedo utilizar una versión más reciente del marco

muchas gracias.

Respuesta

46

¿Qué tal:

public static Predicate<T> Or<T>(params Predicate<T>[] predicates) 
{ 
    return delegate (T item) 
    { 
     foreach (Predicate<T> predicate in predicates) 
     { 
      if (predicate(item)) 
      { 
       return true; 
      } 
     } 
     return false; 
    }; 
} 

está completo y si:

public static Predicate<T> And<T>(params Predicate<T>[] predicates) 
{ 
    return delegate (T item) 
    { 
     foreach (Predicate<T> predicate in predicates) 
     { 
      if (!predicate(item)) 
      { 
       return false; 
      } 
     } 
     return true; 
    }; 
} 

A continuación, llame con:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE, StartsWithI)); 

Otra alternativa sería el uso de los delegados de multidifusión y luego dividir el uso GetInvocationList(), luego haz lo mismo. Posteriormente, se podría hacer:

List<string> filteredNames = names.FindAll(Helpers.Or(StartsWithE+StartsWithI)); 

No soy un gran fan de este último enfoque, aunque - se siente como un poco de un abuso de multidifusión.

+0

¡Gracias! Funciona perfecto! – eric

+0

Muy útil. Se necesitan múltiples predicados para un filtro en ICollectionView. – pStan

+0

Toda la función se puede acortar a 'devolver elemento => predicados.Todo (predicado => predicado (elemento));' – NibblyPig

0

Puede crear un tercer predicado que internamente OR los resultados juntos. Creo que podrías hacer esto sobre la marcha con una expresión lambda. Algo como esto (esto no es una expresión lambda como no soy muy bueno con los que snytax):

static bool StartsWithEorI(string s) 
{ 
    return StartsWithE(s) || StartsWithI(s); 
} 
+0

Claro que me gustaría que en este ejemplo hay una gran cantidad de combinaciones. Ni siquiera estoy pensando en combinar tres filtros ... También de nuevo, lamentablemente, puedo usar SOLAMENTE .NET 2.0 – eric

+0

Ah, eso es desafortunado. Estoy seguro de que si tuviera delegaciones anónimas o expresiones lambda, le daría ese "sobre la marcha" que combina la potencia que desea. – AaronLS

0

Usted puede envolver el método predicado en una clase y tienen el constructor acepta una matriz de cadenas para la prueba de :

class StartsWithPredicate 
{ 
    private string[] _startStrings; 
    public StartsWithPredicate(params string[] startStrings) 
    { 
     _startStrings = startStrings; 
    } 
    public bool StartsWith(string s) 
    { 
     foreach (var test in _startStrings) 
     { 
      if (s.StartsWith(test)) 
      { 
       return true; 
      } 
     } 
     return false; 
    } 
} 

entonces usted puede hacer una llamada como esta:

List<string> filtered = names.FindAll((new StartsWithPredicate("E", "I")).StartsWith); 

de esa manera usted puede probar por cualquier combinación de cadenas de entrada sin necesidad de ampliar la base de código con nuevas variaciones del método StartsWith.

1

En .NET 2.0, hay delegados anónimos que se puede utilizar allí:

List<string> filteredNames = names.FindAll(
    delegate(string s) { return StartsWithE(s) OR StartsWithI(s); } 
); 

De hecho, se puede usar para reemplazar sus funciones, así:

List<string> filteredNames = names.FindAll(
    delegate(string s) { return s.StartsWith("E") || s.StartsWith("I"); } 
); 
22

Te supongo podría escribir algo como esto:

Func<string, bool> predicate1 = s => s.StartsWith("E"); 
Func<string, bool> predicate2 = s => s.StartsWith("I"); 
Func<string, bool> combinedOr = s => (predicate1(s) || predicate2(s)); 
Func<string, bool> combinedAnd = s => (predicate1(s) && predicate2(s)); 

... y así sucesivamente.

+0

Buena solución :) –

2

Recientemente encontré una solución similar a este problema, que también podría ser útil. Amplié el método FindAll para las listas, lo que me permite apilar los predicados en las listas, ya que necesitaba:

public static class ExtensionMethods 
{ 
    public static List<T> FindAll<T> (this List<T> list, List<Predicate<T>> predicates) 
    { 
     List<T> L = new List<T>(); 
     foreach (T item in list) 
     { 
      foreach (Predicate<T> p in predicates) 
      { 
       if (!(p (item))) break; 
      } 
      L.Add (item); 
     } 
     return L; 
    } 
} 

devuelve una lista con sólo los elementos que coinciden con todos los predicados dados. Por supuesto, se puede modificar fácilmente a O todos los predicados en lugar de AND. Pero con eso solo uno puede armar una muy buena variedad de combinaciones lógicas.

Uso:

{ 
    List<Predicate<int>> P = new List<Predicate<int>>(); 
    P.Add (j => j > 100); 
    P.Add (j => j % 5 == 0 || j % 7 == 0); 
    P.Add (j => j < 1000); 

    List<int> L = new List<int>() { 0, 1, 2, ... 999, 1000 } 
    List<int> result = L.FindAll (P); 

    // result will contain: 105, 110, 112, 115, 119, 120, ... 994, 995 
} 

Me tomó más tiempo me gustaría admitir para llegar a esto. Pero delega el hombre ... delegados ...

0

Si está utilizando LINQ para db, encontré la gran solución here. Funcionando perfectamente No estoy seguro si funcionará en .Net 2.0 también o no.

0

Habiendo utilizado este patrón extensivamente con el método de matriz 'params' anterior, me intrigó el hecho de que recientemente aprendí sobre el delegado de Multicast. Como los delegados soportan inherentemente una lista (o multidifusión), puede omitir el patrón params [] y simplemente proporcionar un solo delegado a su función Test(). Deberá llamar a GetInvokationList en el Predicado <> suministrado. Ver esto: Multicast delegate of type Func (with return value)?

Cuestiones relacionadas