2012-01-12 13 views
6

Sto progettando una libreria di classi che ha un sacco di metodi di tipo "AssicurarsiXXX". L'idea di questi metodi deve essere chiamata ogni volta che un codice chiamante richiede qualcosa che può richiedere un'inizializzazione specifica per gli argomenti. È simile al metodo EnsureChildControls di ASP.Net, ma con argomenti come discriminatori.Come garantire che una logica del metodo venga eseguita solo una volta per combinazione di argomenti?

Es:

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

} 

Come tale modello sarà riutilizzare in vari punti e ha chiamato molto spesso, devo mantenere le prestazioni come bersaglio. Devo anche avere un metodo sicuro per i thread.

A tal fine, ho scritto una piccola classe di utilità:

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

Il codice di consumo si presenta così:

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

L'uscita è, come previsto:

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 

Ho alcune domande relative a questo approccio:

  1. Penso di aver correttamente bloccato la classe per garantire la sicurezza dei thread, ma ho ragione?
  2. HashSet<int> e il metodo di calcolo dell'hash corretto? Mi chiedo in particolare se la gestione di null è corretta e se posso "hash" un delegato Action in questo modo.
  3. Attualmente i miei metodi supportano solo metodi statici. Come posso passare a un metodo compatibile con l'istanza (aggiungendo l'istanza come discriminatore) senza perdite di memoria?
  4. Esiste un modo per evitare di passare manualmente tutti gli argomenti al metodo di utilità (solo specificando l'azione), senza esplorare lo stacktrace (a causa dell'impatto sulle prestazioni)? Temo che un sacco di bug siano stati introdotti a causa di argomenti mancanti del metodo esterno.

Grazie in anticipo

+1

Questa tecnica "ConfirmOnce" si chiama Memoization: troverai molte cose su Google se la cerchi. –

risposta

5

Questo è essenzialmente un memoize, tranne la vostra funzione è vuoto. Ma le stesse considerazioni per il confronto degli argomenti di input sono ancora valide.

Wes Dyer parla di how to make a generic, multi-argument memoize in this post. L'idea generale è di portare tutti gli argomenti in un tipo anonimo e usarli come chiave del dizionario.

Per quanto riguarda la sicurezza del thread, considerare .NET already has concurrent collections. Non è necessario creare una raccolta non simultanea in una concomitante; basta usare le collezioni fornite.

Per rendere questo lavoro, ad esempio, i metodi senza perdite, mantenere un WeakReference nell'istanza o archiviare un'istanza del memoizer nell'istanza stessa, a seconda di ciò che funziona per voi.

+0

La voce di Wes Dyer è esattamente quello che stavo cercando. Non sapevo che fosse chiamato Memoization. Grazie –

Problemi correlati