2012-01-12 19 views
6

Estoy diseñando una biblioteca de clases que tiene un montón de métodos de tipo "EnsureXXX". La idea de este método debe invocarse siempre que un código de llamada requiera algo más de lo que puede requerir una inicialización específica de los argumentos. Es similar al método EnsureChildControls de ASP.Net, pero con argumentos como discriminadores.¿Cómo asegurar que la lógica del método se ejecute solo una vez por combinación de argumentos?

Ex:

public static class SomeUtilityClass { 
    public static void EnsureSomething(string arg1, int arg2, object arg3) 
    { 
     // Logic should be called once for each args combination 
    } 
} 

public class CallerClass 
{ 
    public void Foo() 
    { 
     SomeUtilityClass.EnsureSomething("mycustomerid", 4, myData.SomeProperty); 
    } 
    public void Foo2() 
    { 
     SomeUtilityClass.EnsureSomething("mycustomerid", 4, myData.SomeProperty); 
    } 

} 

Como tal patrón se reutilizan en varios lugares y llama muy a menudo, tengo que mantener el rendimiento como un objetivo. También tengo que tener un método seguro para subprocesos.

Para este fin, escribí una pequeña clase de utilidad:

public sealed class CallHelper 
{ 
    private static readonly HashSet<int> g_YetCalled = new HashSet<int>(); 
    private static readonly object g_SyncRoot = new object(); 

    public static void EnsureOnce(Type type, Action a, params object[] arguments) 
    { 
     // algorithm for hashing adapted from http://stackoverflow.com/a/263416/588868 
     int hash = 17; 
     hash = hash * 41 + type.GetHashCode(); 
     hash = hash * 41 + a.GetHashCode(); 
     for (int i = 0; i < arguments.Length; i++) 
     { 
      hash = hash * 41 + (arguments[i] ?? 0).GetHashCode(); 
     } 

     if (!g_YetCalled.Contains(hash)) 
     { 
      lock (g_SyncRoot) 
      { 
       if (!g_YetCalled.Contains(hash)) 
       { 
        a(); 
        g_YetCalled.Add(hash); 
       } 
      } 
     } 
    } 
} 

El código de consumir es así:

public static class Program 
{ 
    static void Main() 
    { 
     SomeMethod("1", 1, 1); 
     SomeMethod("2", 1, 1); 
     SomeMethod("1", 1, 1); 
     SomeMethod("1", 1, null); 

     Console.ReadLine(); 
    } 

    static void SomeMethod(string arg1, int arg2, object arg3) 
    { 
     CallHelper.EnsureOnce(typeof(Program),()=> 
     { 
      Console.WriteLine("SomeMethod called only once for {0}, {1} and {2}", arg1, arg2, arg3); 
     }, arg1, arg2, arg3); 
    } 
} 

La salida es, como se esperaba:

SomeMethod called only once for 1, 1 and 1 
SomeMethod called only once for 2, 1 and 1 
SomeMethod called only once for 1, 1 and 

Tengo algunas preguntas relacionadas con este enfoque:

  1. Creo que he bloqueado correctamente la clase para garantizar la seguridad del hilo, pero ¿estoy bien?
  2. ¿Está HashSet<int> y mi método de cálculo del hash es correcto? Me pregunto especialmente si el manejo de null es correcto, y si puedo "cortar" un delegado Action de esta manera.
  3. Actualmente, mis métodos solo admiten métodos estáticos. ¿Cómo puedo pasar a un método compatible con la instancia (agregando la instancia como un discriminador) sin pérdida de memoria?
  4. ¿Hay alguna manera de evitar pasar todos los argumentos manualmente al método de utilidad (simplemente especificando la acción), sin explorar la pila (debido al impacto en el rendimiento)? Me temo que se introdujeron muchos errores debido a la falta de argumentos del método externo.

Gracias de antemano

+1

Esta técnica de "EnsureOnce" se llama Memoization: encontrará muchas cosas en Google si las busca. –

Respuesta

5

Esto es esencialmente una memoize, excepto que su función es nula. Pero las mismas consideraciones para la comparación de los argumentos de entrada siguen siendo válidas.

Wes Dyer discute how to make a generic, multi-argument memoize in this post. La idea general es pasar todos los argumentos a un tipo anónimo y usarlos como la clave del diccionario.

En cuanto a hacer este thread-safe, considere que .NET already has concurrent collections. No es necesario crear una colección no simultánea en una simultánea; solo use las colecciones provistas.

Para hacer que esto funcione, por ejemplo, los métodos sin fugas, mantenga un WeakReference en la instancia o almacene una instancia del memoial en la instancia en sí, lo que funcione para usted.

+0

La entrada de Wes Dyer es exactamente lo que estaba buscando. No sabía que se llamaba Memoization. Gracias –

Cuestiones relacionadas