Работа с event Action, отписка от события

100
16 апреля 2021, 05:40
public event Action<GameObject> SelectCallbackMethods;
void Start()
{
    SelectCallbackMethods += (obj) => CharacterController.Instance.SelectObj(obj.gameObject);
}
void OnMouseUp()
{
    SelectCallbackMethods.Invoke(this.gameObject);
    SelectCallbackMethods -= (obj) => CharacterController.Instance.SelectObj(obj.gameObject);
}

Как в такой ситуации можно отписать метод? Отписка, используя (-=) после Invoke не работает, метод все равно вызывается.

Правильный ли это подход использования ивентов? Нашел решение: создание переменной для каждого Action, но имеет ли это смысл? Не то же самое ли это, если бы я просто вызывал метод в нужный момент из этого класса:

Action<GameObject> handler;
void Start()
{
    handler = (obj) => CharacterController.Instance.SelectObj(obj.gameObject);
    SelectCallbackMethods += handler;
}
void OnMouseUp()
{
    SelectCallbackMethods.Invoke(this.gameObject);
    SelectCallbackMethods -= handler;
}
Answer 1

Причина такого поведения проста: каждый анонимный метод компилятор разворачивает в отдельный метод.

Т. е. из

using System;
public class Program
{
    public event Action<object> MyEvent;
    void Start()
    {
        MyEvent += obj => Console.WriteLine(obj);
    }
    void OnMouseUp()
    {
        MyEvent.Invoke(this);
        MyEvent -= obj => Console.WriteLine(obj);
    }
}

получится примерно следующее

using System;
public class Program
{
    private sealed class auxiliaryClass
    {
        public static readonly auxiliaryClass instance = new auxiliaryClass();
        public static Action<object> method1;
        public static Action<object> method2;
        internal void method1_Start(object obj)
        {
            Console.WriteLine(obj);
        }
        internal void method2_OnMouseUp(object obj)
        {
            Console.WriteLine(obj);
        }
    }
    public event Action<object> MyEvent;
    private void Start()
    {
        MyEvent += method1 ?? (method1 = auxiliaryClass.method1_Start);
    }
    private void OnMouseUp()
    {
        this.m_MyEvent(this);
        MyEvent -= method2 ?? (method2 = auxiliaryClass.method2_OnMouseUp);
    }
}

я опустил несущественные в данном вопросе детали, дословный код можно увидеть здесь

Соответственно, при отписке от события, method2 в нем не будет найден и это просто проигнорируется, method1 так и останется в числе подписчиков.

Чтобы решить эту проблему вам нужно явно вынести обработчик в отдельный метод:

using System;
public class Program
{
    public event Action<object> MyEvent;
    void Start()
    {
        MyEvent += MyAction;
    }
    void OnMouseUp()
    {
        MyEvent.Invoke(this);
        MyEvent -= MyAction;
    }
    void MyAction(object obj) => Console.WriteLine(obj);
}

демонстрация

Ну или переписать код вообще без использования события, например, с простым bool-флагом:

using System;
public class Program
{
    public event Action<object> MyEvent;
    bool handled;
    void Start()
    {
        handled = false;
    }
    void OnMouseUp()
    {
        if (handled) return;
        handled = true;
        MyAction(this);
    }
    void MyAction(object obj) => Console.WriteLine(obj);
}
READ ALSO
Как объединить и сложить статистику просмотров по дням из двух таблиц?

Как объединить и сложить статистику просмотров по дням из двух таблиц?

Нужно объединить статистику из двух таблиц с группировкой по дням (day)p1, p2 - разделы сайта, здесь только для наглядности

130
Отклонение в доступе к файлам

Отклонение в доступе к файлам

file_get_contents: failed to open stream: Permission denied

77