2012-06-01 14 views
6

Estoy tratando de encontrar una buena manera de aplicar acumulativamente hasta 5 Func's al mismo IEnumerable. Esto es lo que ocurrió:Necesito && juntos un número indeterminado de Func <TEntity, bool>

private Func<SurveyUserView,bool> _getFilterLambda(IDictionary<string, string> filters) 
{ 
    Func<SurveyUserView, bool> invokeList = delegate(SurveyUserView surveyUserView) 
    { 
     return surveyUserView.deleted != "deleted"; 
    }; 

    if (filters.ContainsKey("RegionFilter")) 
    { 
     invokeList += delegate(SurveyUserView surveyUserView) 
     { 
      return surveyUserView.Region == filters["RegionFilter"]; 
     }; 
    } 

    if (filters.ContainsKey("LanguageFilter")) 
    { 
     invokeList += delegate(SurveyUserView surveyUserView) 
     { 
      return surveyUserView.Locale == filters["LanguageFilter"]; 
     }; 
    } 

    if (filters.ContainsKey("StatusFilter")) 
    { 
     invokeList += delegate(SurveyUserView surveyUserView) 
     { 
      return surveyUserView.Status == filters["StatusFilter"]; 
     }; 
    } 

    if (filters.ContainsKey("DepartmentFilter")) 
    { 
     invokeList += delegate(SurveyUserView surveyUserView) 
     { 
      return surveyUserView.department == filters["DepartmentFilter"]; 
     }; 
    } 

    return invokeList; 
} 

pensé que sería aplicarlos de manera acumulativa, sin embargo, puedo ver en los resultados que en realidad es simplemente la aplicación de la última (DepartmentFilter).

Existen 2^4 combinaciones posibles de modo que las fuerzas brutas no funcionen. (Quiero Y utilizando una lambda particular solo cuando la clave correspondiente está presente en el Diccionario.)

EDIT: Aquí está la solución que acepté, pero causa una StackOverflowException cuando se evalúa. ¿Alguien ve por qué?

private Func<SurveyUserView,bool> _getFilterLambda(IDictionary<string, string> filters) 
    { 

     Func<SurveyUserView, bool> resultFilter = (suv) => suv.deleted != "deleted";               

     if (filters.ContainsKey("RegionFilter")) 
     { 
      Func<SurveyUserView, bool> newFilter = 
       (suv) => resultFilter(suv) && suv.Region == filters["RegionFilter"]; 
      resultFilter = newFilter; 
     } 

     if (filters.ContainsKey("LanguageFilter")) 
     { 
      Func<SurveyUserView, bool> newFilter = 
       (suv) => resultFilter(suv) && suv.Locale == filters["LanguageFilter"]; 
      resultFilter = newFilter; 
     } 

     if (filters.ContainsKey("StatusFilter")) 
     { 
      Func<SurveyUserView, bool> newFilter = 
       (suv) => resultFilter(suv) && suv.Status == filters["StatusFilter"]; 
      resultFilter = newFilter; 
     } 

     if (filters.ContainsKey("DepartmentFilter")) 
     { 
      Func<SurveyUserView, bool> newFilter = 
       (suv) => resultFilter(suv) && suv.department == filters["DepartmentFilter"]; 
      resultFilter = newFilter; 
     } 

     return resultFilter; 
    } 

EDIT: Aquí está el muy agradable explicación de por qué esto dio lugar a una StackOverflowException de amigo y mentor Chris Flather-

Lo importante para entender por qué se produce el bucle infinito es la comprensión de que los símbolos en una lambda se resuelve (es decir, en tiempo de ejecución y no en definición).

Tome este ejemplo simplificado:

Func<int, int> demo = (x) => x * 2; 
Func<int, int> demo2 = (y) => demo(y) + 1; 
demo = demo2; 
int count = demo(1); 

Si se resuelve de forma estática en la definición esto iba a funcionar y ser el mismo que:

Func<int, int> demo2 = (y) => (y * 2) + 1; 
Int count = demo2(1); 

Pero en realidad no intentar averiguar qué la demo incorporada en demo2 funciona hasta el tiempo de ejecución, en cuyo momento demo2 se ha redefinido como demo. En esencia, el código ahora se lee:

Func<int, int> demo2 = (y) => demo2(y) + 1; 
Int count = demo2(1); 

Respuesta

4

En lugar de tratar de combinar los delegados de esta manera, se podría construir nuevos delegados que utilizan el existente con su condición AND:

Func<SurveyUserView, bool> resultFilter = (suv) => true; 

if (filters.ContainsKey("RegionFilter")) 
{ 
    var tmpFilter = resultFilter; 
    // Create a new Func based on the old + new condition 
    resultFilter = (suv) => tmpFilter(suv) && suv.Region == filters["RegionFilter"]; 
} 

if (filters.ContainsKey("LanguageFilter")) 
{ 
    // Same as above... 

//... Continue, then: 

return resultFilter; 

Dicho esto, se puede ser más fácil pasar su original IQueryable<SurveyUserView> o IEnumerable<SurveyUserView> en este método, y simplemente agregar .Where cláusulas directamente al filtro. A continuación, puede devolver la consulta final sin ejecutarla, con los filtros añadidos.

+1

¡ADVERTENCIA! Esto causó una excepción de desbordamiento de pila cuando lo implementé. (Lo cual es un poco irónico. :)) La solución de Reed tiene buen sentido lógico, pero su comentario en la parte inferior (encadenando las cláusulas Where) parece ser la única manera de ir aquí. –

+0

@TreyCarroll ¿Lo implementó de la misma manera que la anterior, utilizando una variable temporal para la nueva lambda? –

+0

Sí. Seguí tu patrón exactamente. Recién confirmado que es arrojar SOE. –

2

Me gustaría pensar que el uso de la extensión Where(...) en lo que es, probablemente, una IQueryable<SurveyUserView> y devolver una IQueryable<SurveyUserView> en lugar de un Func<...>:

// Assuming `q` is a `IQueryable<SurveyUserView>` 

if(filters.ContainsKeys["Whatever"]) 
{ 
    q = q.Where(suv => suv.Status == filters["Whatever"]; 
} 

El And ING está implícito.

+0

¿Qué es la extensión "Y"? ¿Quisiste decir? ¿Dónde (...)? –

+0

Escribí mal. Lo editaré. Estaba pensando 'Expression' cosas, no simple' IQueryable's. – bluevector

+0

+1 encadena el 'Dónde' - tan fácil. –

2
private Func<SurveyUserView, bool> _getFilterLabda(IDictionary<string, string> filters) 
    { 
     Func<SurveyUserView, bool> invokeList = surveyUserView => surveyUserView.deleted != "deleted"); 

     if (filters.ContainsKey("RegionFilter")) 
     { 
      invokeList += surveyUserView => surveyUserView.Region == filters["RegionFilter"]); 
     } 

     if (filters.ContainsKey("LanguageFilter")) 
     { 
      invokeList += surveyUserView => surveyUserView.Locale == filters["LanguageFilter"]; 
     } 

     if (filters.ContainsKey("StatusFilter")) 
     { 
      invokeList += surveyUserView => surveyUserView.Status == filters["StatusFilter"]; 
     } 

     if (filters.ContainsKey("DepartmentFilter")) 
     { 
      invokeList += surveyUserView => surveyUserView.department == filters["DepartmentFilter"]); 
     } 

     return invokeList; 
    } 
    ... 
    Func<SurveyUserView, bool> resultFilter = suv => _getFilterLabda(filters) 
     .GetInvocationList() 
     .Cast<Func<SurveyUserView, bool>>() 
     .All(del => del(suv)) 
+0

Un solo 'Func ' es suficiente, no necesita una lista de delegados de multidifusión. Ver el miembro 'GetInvocationList()'. –

+0

@BenVoigt, si su propósito es agregar los resultados de estas funciones 'bool', aunque puede crear un único delegado monolítico con' + = ', estaría creando un delegado al que nunca querría llamar directamente , lo que para mí parece bastante confuso desde una perspectiva de mantenimiento. El uso de 'List >' lo evita de forma bastante limpia. –

+0

@BenVoigt Gracias ... Cambió –

1

Este es mi método favorito para lograr lo que está pidiendo.

private Func<SurveyUserView, bool> _getFilterLambda(IDictionary<string, string> filters) 
{ 
    List<Func<SurveyUserView, bool>> invokeList = new List<Func<SurveyUserView, bool>>(); 

    invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.deleted != "deleted"); 

    if (filters.ContainsKey("RegionFilter")) 
    { 
     invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Region == filters["RegionFilter"]); 
    } 

    if (filters.ContainsKey("LanguageFilter")) 
    { 
     invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Locale == filters["LanguageFilter"]); 
    } 

    if (filters.ContainsKey("StatusFilter")) 
    { 
     invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Status == filters["StatusFilter"]); 
    } 

    if (filters.ContainsKey("DepartmentFilter")) 
    { 
     invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.department == filters["DepartmentFilter"]); 
    } 

    return delegate (SurveyUserView surveyUserView) 
    { 
     bool unfiltered = true; 

     foreach(var filter in invokeList) 
     { 
      unfiltered = unfiltered && filter(surveyUserView); 
     } 

     return unfiltered; 
    }; 
} 

Elaboramos una lista de cada uno de los delegados que desea presentar; y luego devuelve otro delegado separado que itera sobre esa lista combinando cada uno de los filtros con un AND lógico simple.

Esto funciona porque el delegado que estamos devolviendo cierra sobre invokeList; creando un tipo de variable privada que almacena todos nuestros nuevos delegados que viajan con nuestro delegado devuelto.

Una alternativa que es un poco más cerca sintácticamente a su original es:

private Func<SurveyUserView, bool> _getFilterLambda(IDictionary<string, string> filters) 
{ 
    Func<SurveyUserView, bool> invokeList = (SurveyUserView surveyUserView) => surveyUserView.deleted != "deleted"; 

    if (filters.ContainsKey("RegionFilter")) 
    { 
     invokeList += (SurveyUserView surveyUserView) => surveyUserView.Region == filters["RegionFilter"]; 
    } 

    if (filters.ContainsKey("LanguageFilter")) 
    { 
     invokeList += (SurveyUserView surveyUserView) => surveyUserView.Locale == filters["LanguageFilter"]; 
    } 

    if (filters.ContainsKey("StatusFilter")) 
    { 
     invokeList += (SurveyUserView surveyUserView) => surveyUserView.Status == filters["StatusFilter"]; 
    } 

    if (filters.ContainsKey("DepartmentFilter")) 
    { 
     invokeList += (SurveyUserView surveyUserView) => surveyUserView.department == filters["DepartmentFilter"]; 
    } 

    return delegate (SurveyUserView surveyUserView) 
    { 
     bool unfiltered = true; 

     // implicit cast from Delegate to Func<SurveyUserView, bool> happening on next line 
     foreach (Func<SurveyUserView, bool> filter in invokeList.GetInvocationList()) 
     { 
      unfiltered = unfiltered && filter(surveyUserView); 
     } 

     return unfiltered; 
    }; 
} 

En esta versión que estamos realmente sólo usando invokeList como una lista de los delegados; llamamos a GetInvocationList() (un método de la clase de delegado de la que proviene Func) para obtener una lista de todos los delegados que se combinan para formar el delegado de multidifusión.

Yo personalmente prefiero la primera versión porque es más claro lo que está sucediendo detrás de las escenas.

Ambos son realmente los mismos que Jacob Seleznev's answer que de alguna manera me perdí antes de responder. Solo traen al delegado final dentro del método para que el método en sí mismo aún satisfaga el contrato original de Trey.

Finalmente, si todos los filtros son independientes de la orden y no tienen efectos secundarios, podemos escribir una versión que ejecutará los filtros en paralelo.

private Func<SurveyUserView, bool> _getFilterLambdaParallel(IDictionary<string, string> filters) 
{ 
    List<Func<SurveyUserView, bool>> invokeList = new List<Func<SurveyUserView, bool>>(); 

    invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.deleted != "deleted"); 

    if (filters.ContainsKey("RegionFilter")) 
    { 
     invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Region == filters["RegionFilter"]); 
    } 

    if (filters.ContainsKey("LanguageFilter")) 
    { 
     invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Locale == filters["LanguageFilter"]); 
    } 

    if (filters.ContainsKey("StatusFilter")) 
    { 
     invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.Status == filters["StatusFilter"]); 
    } 

    if (filters.ContainsKey("DepartmentFilter")) 
    { 
     invokeList.Add((SurveyUserView surveyUserView) => surveyUserView.department == filters["DepartmentFilter"]); 
    } 

    return delegate (SurveyUserView surveyUserView) 
    { 
     int okCount = 0; 
     Parallel.ForEach(invokeList, delegate (Func<SurveyUserView, bool> f) 
     { 
      if (f(surveyUserView)) 
      { 
       System.Threading.Interlocked.Increment(ref okCount); 
      } 
     }); 
     return okCount == invokeList.Count; 
    }; 
} 

Usamos Parallel.ForEach para ejecutar los filtros en paralelo. Hay una pequeña complicación que nos impide usar nuestro booleano simple Y - no hay garantía de que la Y lógica ocurra de manera atómica creando una desagradable condición de carrera.

Para solucionar esto simplemente contamos el número de filtros que se pasaron utilizando Interlocked.Increment que se garantiza que es atómico. Si todos los filtros pasaron con éxito, entonces sabemos que podemos devolver la verdad; de lo contrario, el y habría fallado.

El equivalente para hacer un O lógico aquí debería comprobar con okCount era más que cero.

Cuestiones relacionadas