Присоединить обработчик события, который будет вызван только один раз - PullRequest
0 голосов
/ 09 октября 2018

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

Я пытаюсь что-то вроде этого:

public static void AttachOnce<TEventArgs>([NotNull] this EventHandler<TEventArgs> me, [NotNull] Action<object, TEventArgs> action)
    where TEventArgs : System.EventArgs
{
    var handler = me;
    EventHandler<TEventArgs> wrappedAction = null;
    wrappedAction = (sender, args) =>
    {
        action(sender, args);
        handler -= wrappedAction;
    };
    handler += wrappedAction;
}

Но ReSharper жалуется на отказ от подписки, что handler означает «Доступ к измененному закрытию» .

Я знаю, что это значит, поэтому я сделал локальную переменную длязакрытие уже, но это, кажется, не решает это.Что здесь не так?

Прямой жесткий код работает.Примерно так:

var file = new FileSystemWatcher("path/to/file");

FileSystemEventHandler handler = null;
handler = (sender, args) =>
{
    // My action code here
    file.Changed -= handler;
};
file.Changed += handler;

РЕДАКТИРОВАТЬ 1 (2018-10-09 11:43 CET):

Возможно, я просто слишком быстро задал вопрос, прежде чем тщательно его обдумать.
Вы не можете создавать методы расширения для событий.Совсем.Это просто невозможно в C #.Поэтому я даже не могу проверить, почему ReSharper жалуется, и если это правильно, потому что такой вызов, как file.Changed.AttachOnce(action), недопустим. «Событие« Изменено »может появиться только в левой части + = или - =» .

Я нашел еще несколько источников для похожих запросов / вопросов:
http://www.hardkoded.com/blog/csharp-wishlist-extension-for-events
Одноразовый общий вызов события?

Ответы [ 3 ]

0 голосов
/ 09 октября 2018

Не уверен, как именно вы хотите разработать метод расширения, но, возможно, это поможет вам начать:

public static void OnChangedOnce(this FileSystemWatcher instance, Action<object, FileSystemEventArgs> action)
{
    var file = instance;
    var wrappedAction = action;
    FileSystemEventHandler handler = null;
    handler = (object sender, FileSystemEventArgs args) =>
    {
        wrappedAction(sender, args);
        file.Changed -= handler;
    };
    file.Changed += handler;
    file.EnableRaisingEvents = true; // mandatory
}

var file = new FileSystemWatcher("path/to/file");
file.OnChangedOnce((sender, args)) =>
{
    // your action here
});
0 голосов
/ 10 октября 2018

Я думал о другом, но гораздо более простом подходе, использующем «автономный» встроенный обработчик, который будет использоваться следующим образом:

obj.Event += (s, e) =>
{
    Detach(obj, nameof(obj.Event));
    // ..do stuff..
};

Метод Detach будет выглядеть следующим образоми может быть помещен где угодно (скорее всего, статический вспомогательный класс):

public static void Detach(object obj, string eventName)
{
    var caller = new StackTrace().GetFrame(1).GetMethod();
    var type = obj.GetType();
    foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
    {
        if (typeof(Delegate).IsAssignableFrom(field.FieldType))
        {
            var handler = (field.GetValue(obj) as Delegate)?.GetInvocationList().FirstOrDefault(m => m.Method.Equals(caller));
            if (handler != null)
            {
                type.GetEvent(eventName).RemoveEventHandler(obj, handler);
                return;
            }
        }
    }
}

Так что для вашего примера код будет выглядеть так:

file.Changed += (s, e) =>
{
    Detach(file, nameof(file.Changed));
    // My action code here
};
0 голосов
/ 09 октября 2018

В этом случае все в порядке.ReSharper просто предупреждает, что handler отличается от времени, которое вы объявляете, и времени, когда оно выполняется.

...