Как обеспечить, чтобы логика метода выполнялась только один раз для комбинации аргументов? - PullRequest
6 голосов
/ 12 января 2012

Я проектирую библиотеку классов, в которой есть несколько методов типа "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

У меня есть несколько вопросов, связанных с этим подходом:

  1. Я думаю, что правильно заблокировал класс для обеспечения безопасности потока,но я прав?
  2. Правильно ли HashSet<int> и мой метод вычисления хэша?Мне особенно интересно, правильна ли обработка null, и могу ли я "хэшировать" делегирование Action таким образом.
  3. Мои методы в настоящее время поддерживают только статические методы.Как я могу перейти на метод, совместимый с экземпляром (добавив экземпляр как дискриминатор) без утечки памяти?
  4. Есть ли способ избежать передачи всех аргументов вручную служебному методу (просто указав действие), не исследуятрассировка стека (из-за влияния на производительность)?Боюсь, что появилось много ошибок из-за отсутствия аргументов внешнего метода.

Заранее спасибо

1 Ответ

5 голосов
/ 12 января 2012

Это, по сути, памятка , за исключением того, что ваша функция недействительна. Но те же соображения для сравнения входных аргументов остаются в силе.

Уэс Дайер обсуждает , как сделать общий, с несколькими аргументами, заметку в этом посте . Общая идея состоит в том, чтобы свернуть все аргументы в анонимный тип и использовать его в качестве ключа словаря.

Что касается обеспечения безопасности этого потока, учтите, что .NET уже имеет одновременные коллекции . Вам не нужно превращать не параллельную коллекцию в параллельную; просто используйте предоставленные коллекции.

Чтобы это работало для методов экземпляра без утечки, либо сохраните WeakReference для экземпляра, либо сохраните экземпляр памятки в самом экземпляре, в зависимости от того, что вам подходит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...