2012-01-12 10 views
6

"EnsureXXX"という種類のメソッドを持つクラスライブラリを設計しています。このメソッドの考え方は、呼び出しコードが引数に固有の初期化を必要とする以上のものを必要とするときはいつでも呼び出されます。それはASP.NetのEnsureChildControlsの方法と似ていますが、弁別子としての引数があります。メソッドのロジックが引数の組み合わせごとに1回だけ実行されるようにする方法はありますか?

例:このようなパターンはいくつかの場所で再利用し、非常に頻繁に呼ばれるように

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

} 

、私は、ターゲットとしての性能を維持する必要があります。スレッドセーフな方法が必要です。この目的のために

は、私は小さなユーティリティクラスを書いた:

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

のかかるコードは次のようになります。

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

期待通りの出力は、次のとおりです。

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 

このアプローチに関するいくつかの質問があります:

  1. スレッドの安全性を確保するためにクラスを適切にロックしていると思いますが、正しいのですか?
  2. HashSet<int>で、ハッシュを計算する私の方法は正しいですか?私は特にnullの取り扱いが正しいかどうか、そしてもし私がAction代理人をこの方法で "ハッシュ"することができるかどうか疑問に思っています。
  3. 私のメソッドは現在、静的メソッドのみをサポートしています。メモリを漏らさずに、インスタンスを互換性のあるメソッド(識別子を識別器としてインスタンスに追加する)に移動するにはどうすればよいですか?
  4. ユーティリティのメソッド(アクションを指定するだけ)にすべての引数を手動で渡すことを回避する方法はありますか(パフォーマンスの影響により)?私は、outermethodからの引数の欠落のために導入されたバグの多くを恐れています。

あなたの関数がvoid以外事前

+1

この「EnsureOnce」テクニックはMemoizationと呼ばれています。検索するとGoogleにたくさんのものが見つかります。 –

答えて

5

に感謝。これは、基本的にmemoizeです。しかし、入力引数の比較についても同じ考慮事項が有効です。

ウェールズダイアーは、how to make a generic, multi-argument memoize in this postについて議論します。一般的な考え方は、すべての引数を匿名型にロールし、それを辞書のキーとして使用することです。

このスレッドセーフについては、.NET already has concurrent collectionsとしてください。同時でないコレクションを非同期コレクションにする必要はありません。提供されたコレクションを使用してください。

このメソッドをリークすることなくインスタンスメソッドにするには、WeakReferenceをそのままインスタンスに格納するか、インスタンス自体にmemoizerのインスタンスを格納します。

+0

Wes Dyerのエントリーはまさに私が探していたものです。私はそれがMemoizationと呼ばれていたことを知らなかった。ありがとう –

関連する問題