Я проектирую библиотеку классов, в которой есть несколько методов типа "EnsureXXX".Идея этих методов заключается в том, чтобы вызывать всякий раз, когда вызывающему коду требуется нечто, что может потребовать инициализации, специфичной для аргументов.Это похоже на EnsureChildControls
метод ASP.Net, но с аргументами в качестве дискриминаторов.
Пример:
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
У меня есть несколько вопросов, связанных с этим подходом:
- Я думаю, что правильно заблокировал класс для обеспечения безопасности потока,но я прав?
- Правильно ли
HashSet<int>
и мой метод вычисления хэша?Мне особенно интересно, правильна ли обработка null
, и могу ли я "хэшировать" делегирование Action
таким образом. - Мои методы в настоящее время поддерживают только статические методы.Как я могу перейти на метод, совместимый с экземпляром (добавив экземпляр как дискриминатор) без утечки памяти?
- Есть ли способ избежать передачи всех аргументов вручную служебному методу (просто указав действие), не исследуятрассировка стека (из-за влияния на производительность)?Боюсь, что появилось много ошибок из-за отсутствия аргументов внешнего метода.
Заранее спасибо