Почему при попытке отписаться от события объект обработчика события не распознается?

Это оказался довольно длинный вопрос, поэтому заранее спасибо всем тем, кто не пожалеет своего времени, чтобы прочитать его и прокомментировать/ответить :)


Правки

  • Этот Вопрос был значительно упрощен.
  • Пример кода теперь представляет собой законченную простую программу.


Я использую шаблон наблюдателя, реализованный через интерфейсы:

public interface IObserver<in T>where T:EventArgs
{
    void Update(object sender, T e);
}

public interface ISubject<in T, TU>where TU:EventArgs
{
    event EventHandler<TU> Notify;

    T State { set; }

    void Attach(Action<object,TU> callback);
    void Detach(Action<object, TU> callback);
}


Я создал два простых класса, которые реализуют эти интерфейсы. Объект MyObserver просто выводит строку в окно консоли, когда в объекте MySubject вызывается событие Notify.

    public class MyObserver:IObserver<TestEventArgs>
    {
        private ISubject<bool, TestEventArgs> _subject;

        public MyObserver(ISubject<bool, TestEventArgs> subject)
        {
            _subject = subject;
        }

        public void Subscribe()
        {
            _subject.Attach(Update);
        }

        public void Unsubscribe()
        {
            _subject.Detach(Update);
        }

        public void Update(object sender, TestEventArgs e)
        {
            Console.WriteLine(e.TestMessage);
        }
    }

    public class MySubject:ISubject<bool, TestEventArgs>
    {
        public void ObservableEvent(string message)
        {
            InvokeNotify(message);
        }

        private void InvokeNotify(string message)
        {
            EventHandler<TestEventArgs> handler = Notify;

            if(handler != null)
            {
                handler(this, new TestEventArgs(message));
            }
        }

        public event EventHandler<TestEventArgs> Notify;

        public bool State
        {
            set { throw new NotImplementedException(); }
        }

        public void Attach(Action<object, TestEventArgs> callback)
        {
            Notify += new EventHandler<TestEventArgs>(callback);
        }

        public void Detach(Action<object, TestEventArgs> callback)
        {
            Notify -= new EventHandler<TestEventArgs>(callback);
        }
    }

    public class TestEventArgs:EventArgs
    {
        public TestEventArgs(string message)
        {
            TestMessage = message;
        }

        public string TestMessage { get; private set; }
    }


Эта тестовая программа показывает, что:

  • до того, как myObserver подпишется на событие, сообщение не выводится в окно консоли.
  • после того, как myObserver подписался на событие Notify, сообщение выводится в окно консоли.
  • после того, как myObserver отменил подписку на событие Notify, сообщение по-прежнему выводится в окно консоли

    static void Main(string[] args)
    {
        MySubject mySubject = new MySubject();
        MyObserver myObserver = new MyObserver(mySubject);
    
        //we have not subscribed to the event so this should not be output to the console
        mySubject.ObservableEvent("First Test");
    
        myObserver.Subscribe();
    
        //we are now subscribing to the event. This should be displayed on the console window
        mySubject.ObservableEvent("Second Test");
    
        myObserver.Unsubscribe();
    
        //We have unsubscribed from the event. I would not expect this to be displayed
        //...but it is!
        mySubject.ObservableEvent("Third Test");
    
        Console.ReadLine();
    }
    

Моя проблема заключается в том, что процесс отмены подписки не работает.

Я действительно не понимаю, почему.


Вопросы

  • Почему не работает процесс отписки?
  • Что происходит при сравнении двух обработчиков событий? Как они определяются как равные или нет? Это может привести к ответу на вопрос, почему метод списка вызовов Contains всегда возвращает false.

person Lewray    schedule 15.09.2011    source источник
comment
гм... инфраструктура .NET уже реализует шаблон наблюдателя. Зачем ты делаешь здесь новое колесо? Домашнее задание?   -  person Brian Driscoll    schedule 15.09.2011
comment
Здесь много кода. Пожалуйста, попробуйте создать короткую, но полную программу, демонстрирующую проблему без всего окружающего багажа.   -  person Jon Skeet    schedule 15.09.2011
comment
@Brian: Я задавался тем же вопросом и не спрашивал об этом, потому что я не читал весь вопрос и мог что-то пропустить. Так или иначе, события в .NET являются реализацией шаблона наблюдателя, так что, по сути, вы, Льюрэй, используете одну реализацию шаблона наблюдателя для реализации другой?   -  person Daniel Hilgarth    schedule 15.09.2011
comment
@Brian - Fair Point, я думаю, после того, как у меня возникла идея, я немного увлекся ее реализацией и не подумал проверить, существует ли существующая реализация. Я не совсем новичок в C# и .Net, но мне еще есть чему поучиться! - но, глядя на .NET IObservable‹T›... почему есть метод Subscribe, но нет Unsubscribe?   -  person Lewray    schedule 15.09.2011
comment
@Jon - Я знаю, багажа много. Я посмотрю, смогу ли я воспроизвести в более простой среде и опубликую результат как редактирование или что-то в этом роде.   -  person Lewray    schedule 15.09.2011
comment
@Daniel - По сути, я повторял один и тот же код для реализации подписки и отказа от подписки. Мне казалось, что это повторение можно было бы лучше контролировать и поддерживать, если бы оно было реализовано в виде интерфейсов. Я бы приветствовал исправление или объяснение того, как я могу упростить сценарий. Я ошибаюсь, создавая интерфейс для инкапсуляции кода, который потребуется в нескольких классах, но реализован в каждом из них немного по-разному?   -  person Lewray    schedule 15.09.2011
comment
@Lewray: я не прочитал ваш вопрос полностью, не говоря уже о коде, но краткий ответ на ваш комментарий: (1) правильно использовать интерфейс (2) вы можете контролировать добавление и удаление обработчиков даже с помощью встроенные события, а именно с синтаксисом свойства, который вы уже используете для своего события Notify.   -  person Daniel Hilgarth    schedule 15.09.2011
comment
@Jon Я сделал все возможное, чтобы упростить ситуацию. Я был бы очень признателен за любой ваш вклад. Спасибо за ваше время.   -  person Lewray    schedule 15.09.2011
comment
@Lewray: Вы действительно не можете показать это менее чем в 6 типах? Я посмотрю, когда смогу, но чем меньше код, который мне нужно посмотреть, тем меньше времени мне нужно найти :)   -  person Jon Skeet    schedule 15.09.2011
comment
@Lewray: IObservable<T>.Subscribe возвращает IDisposable, который откажется от подписки, когда вы его утилизируете, IIRC.   -  person Jon Skeet    schedule 15.09.2011


Ответы (1)


Я подозреваю, что ваша проблема в том, что этот код:

public void Attach(Action<object, TestEventArgs> callback)
{
    Notify += new EventHandler<TestEventArgs>(callback);
}

На самом деле выделяет новый объект, как и соответствующий код Detach. Так что то, что отсоединяется, не то же самое, что то, что прикрепляется.

Я не уверен, но вы могли бы исправить это, изменив свои Attach и Detach так, чтобы они были:

void Attach(EventHandler<TU> callback);
void Detach(EventHandler<TU> callback);

И в клиентском коде:

public void Attach(EventHandler<TestEventArgs> callback)
{
    Notify += callback;
}

public void Detach(EventHandler<TestEventArgs> callback)
{
    Notify -= callback;
}

На самом деле я не пытался скомпилировать это, но похоже, что это должно работать.

Или, если компилятор может выполнить преобразование типов:

public void Attach(Action<object, TestEventArgs> callback)
{
    Notify += callback;
}

Может стоит попробовать.

person Jim Mischel    schedule 15.09.2011
comment
Я прошел через это, и это, безусловно, решает проблему! Я думаю, меня смутила эта статья заявив, что вы можете использовать либо: += new EventHandler(callback);, либо += callback; Но я полагаю, что ключевое отличие состоит в том, что в их примере они используют объект EventHAndler в качестве обратного вызова вместо Action. Спасибо за помощь! - person Lewray; 16.09.2011
comment
Просто для полноты... Последнее предложение не строится, Action<object, TestEventArgs> не может быть неявно приведено к EventHandler<TestEventArgs>. Спасибо еще раз. - person Lewray; 16.09.2011
comment
@Lewray: Рад, что у тебя все заработало. И спасибо за примечание о последнем предложении. Я отредактировал свой ответ. - person Jim Mischel; 16.09.2011