2008-10-17 9 views
7

que tienen un método C#, que acepta un predicado <Foo> y devuelve una lista de artículos a juego ...delegado resultados

public static List<Foo> FindAll(Predicate<Foo> filter) 
{ 
    ... 
} 

El filtro será a menudo una de un conjunto común ...

public static class FooPredicates 
{ 
    public static readonly Predicate<Foo> IsEligible = (foo => ...) 
    ... 
} 

... pero puede ser un delegado anónimo.

Ahora me gustaría tener este método en caché de sus resultados en la caché de ASP.NET, por lo que las llamadas repetidas con el mismo delegado solo devuelven el resultado en caché. Para esto, necesito crear una clave de caché del delegado. ¿Delegate.GetHashCode() producirá resultados razonables para este propósito? ¿Hay algún otro miembro del Delegado que deba ver? ¿Lo harías de otra manera?

Respuesta

4

Para llevar a cabo su tarea de almacenamiento en caché, puede seguir las otras sugerencias y crear un predicado Diccionario < < Foo>, Lista < Foo >> (estática para, o en el campo mundial miembro de otra manera) que almacena en caché los resultados. Antes de ejecutar realmente el Predicado < Foo>, necesitaría verificar si el resultado ya existe en el diccionario.

El nombre general para este almacenamiento en caché función determinista se llama memoization - y su impresionante :)

Desde C# 3.0 añadido lambda de y el botín de los delegados Func/acción, añadiendo memoization a C# es bastante fácil.

Wes Dyer tiene un great post que trae el concepto a C# con algunos excelentes ejemplos.

Si quieres que te muestre cómo hacerlo, avísame ... de lo contrario, la publicación de Wes debería ser adecuada.

En respuesta a su consulta sobre delegar códigos hash. Si dos delegados son iguales, d1.GetHashCode() debería ser igual a d2.GetHashCode(), pero no estoy 100% al respecto. Puede verificar esto rápidamente dando Memoization una vez, y agregando una WriteLine en su método FindAll. Si esto no es cierto, otra opción es usar Linq.Expression < Predicado < Foo >> como parámetro. Si las expresiones no son cierres, las expresiones que hacen lo mismo deben ser iguales.

Dejarme saber cómo va esto, estoy interesado en saber la respuesta sobre delegate.Equals.

+0

He respondido a continuación con algunas pruebas de delegado. Iguales. Probablemente añadiré más el lunes cuando trate de usar las ideas de verdad. – stevemegson

2

La igualdad del delegado examina cada invocación en la lista de invocación, probando la igualdad del método que se invocará y el destino del método.

El método es una pieza simple de la clave de caché, pero el objetivo del método (la instancia para invocarlo, suponiendo un método de instancia) podría ser imposible de almacenar en caché de forma serializable. En particular, para las funciones anónimas que capturan el estado, será una instancia de una clase anidada creada para capturar ese estado.

Si todo esto está en la memoria, solo mantener al delegado como la tecla hash estará bien, aunque puede significar que algunos objetos que los clientes esperarían sean basura recolectada. Si necesita serializar esto en una base de datos, se pone más peludo.

¿Podría hacer que su método acepte una clave de caché (por ejemplo, una cadena) también? (Eso es suponiendo que un caché en la memoria es inadecuado.)

+0

"El método es una pieza simple de la clave de caché". Lástima que .Net simplemente no lo use. El código hash de un delegado (normal) se basa SOLAMENTE en los tipos de delegados en su lista de invocación. Por lo tanto, cualquier delegado 'Action' con 4 suscriptores tiene el mismo código hash = (http://stackoverflow.com/questions/6624151/why-do-2-delegate-instances-return-the-same-hashcode/6624255#6624255 –

1

A menos que esté seguro de que la implementación de GetHashCode del Delegado es determinista y no da lugar a ninguna colisión, no me fiaría de ella.

Aquí hay dos ideas. Primero, almacene los resultados de los delegados dentro de un diccionario Predicado/Lista, usando el predicado como la clave, y luego almacene el diccionario completo de resultados bajo una sola clave en la caché. Lo malo es que pierdes todos tus resultados en caché si el elemento de caché se pierde.

Una alternativa sería crear un método de extensión para Predicate, GetKey(), que utiliza un diccionario de objeto/cadena para almacenar y recuperar todas las claves para todos los Predicados. Indexa en el diccionario con el delegado y devuelve su clave, creando una si no la encuentra. De esta forma, se asegura que está obteniendo la clave correcta por delegado y que no hay colisiones. Un ingenuo sería el nombre de tipo + Guid.

+0

La igualdad de delegados (y el código hash) funciona bien si puede almacenar el delegado como la clave en un diccionario recto. El problema viene si tiene que sacar el mapa de la memoria de alguna manera. –

0

La misma instancia de un objeto siempre devolverá el mismo código hash (requisito de GetHashCode() en .Net). Si sus predicados están dentro de una lista estática y no los está redefiniendo cada vez, no puedo ver un problema al usarlos como claves.

2

Mantener los resultados almacenados en caché en un diccionario < predicado < Foo>, Lista < Foo >> es incómodo para mí porque quiero que la memoria caché de ASP.NET para manejar caducidad para mí en lugar de almacenamiento en caché de todos los resultados para siempre, pero es lo contrario de una buena solución. Creo que terminaré yendo con el diccionario de Will < Predicado < Foo>, string> para almacenar en caché una cadena que pueda usar en la clave de caché de ASP.NET.

Algunas pruebas iniciales sugieren que delegar igualdad hace lo "correcto" como han dicho otros, pero Delegate.GetHashCode no es útil desde el punto de vista patológico. Reflector revela

public override int GetHashCode() 
{ 
    return base.GetType().GetHashCode(); 
} 

Así que cualquier predicado < Foo> devuelve el mismo resultado.

Mi problema restante era cómo funciona la igualdad para los delegados anónimos. ¿Qué significa "el mismo método llamado en el mismo objetivo" entonces? Parece que mientras el delegado se definió en el mismo lugar, las referencias son iguales. Los delegados con el mismo cuerpo definido en diferentes lugares no lo son.

static Predicate<int> Test() 
{ 
    Predicate<int> test = delegate(int i) { return false; }; 
    return test; 
} 

static void Main() 
{ 
    Predicate<int> test1 = Test(); 
    Predicate<int> test2 = Test(); 
    Console.WriteLine(test1.Equals(test2)); // True 

    test1 = delegate(int i) { return false; }; 
    test2 = delegate(int i) { return false; }; 
    Console.WriteLine(test1.Equals(test2)); // False 
} 

Esto debería estar bien para mis necesidades. Las llamadas con los predicados predefinidos se almacenarán en caché. Las llamadas múltiples a un método que llama a FindAll con un método anónimo deben obtener resultados en caché. Dos métodos que llaman a FindAll con aparentemente el mismo método anónimo no compartirán los resultados en caché, pero esto debería ser bastante raro.