C#: събития или интерфейс за наблюдател? Плюсове минуси?

Имам следното (опростено):

interface IFindFilesObserver
{
    void OnFoundFile(FileInfo fileInfo);
    void OnFoundDirectory(DirectoryInfo directoryInfo);
}

class FindFiles
{
    IFindFilesObserver _observer;

    // ...
}

...и съм в конфликт. Това е основно това, което бих написал на C++, но C# има събития. Трябва ли да променя кода, за да използва събития, или трябва да го оставя?

Какви са предимствата или недостатъците на събитията пред традиционния интерфейс за наблюдатели?


person Roger Lipscombe    schedule 15.02.2009    source източник


Отговори (11)


Считайте, че събитието е интерфейс за обратно извикване, когато интерфейсът има само един метод.

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

По-малко поддръжка
Друго добро нещо при събитията е, че можете да добавите ново към конкретен клас, така че да го повиши, и не е нужно да променяте всеки съществуващ наблюдател. Докато, ако искате да добавите нов метод към интерфейс, трябва да обиколите всеки клас, който вече имплементира този интерфейс, и да внедрите новия метод във всички тях. Със събитие обаче трябва само да промените съществуващите класове, които действително искат да направят нещо в отговор на новото събитие, което добавяте.

Моделът е вграден в езика, така че всеки знае как да го използва
Събитията са идиоматични, тъй като когато видите събитие, знаете как да го използвате. С интерфейс за наблюдател хората често прилагат различни начини за регистриране, за да получават известия и да свързват наблюдателя.. със събития обаче, след като се научите как да се регистрирате и използвате такъв (с оператора +=), останалите са всички един и същ.

Професионалисти за интерфейси
Нямам много плюсове за интерфейси. Предполагам, че принуждават някого да внедри всички методи в интерфейса. Но не можете наистина да принудите някого да приложи всички тези методи правилно, така че не мисля, че има голяма стойност в това.

Синтаксис
Някои хора не харесват начина, по който трябва да декларирате тип делегат за всяко събитие. Освен това стандартните манипулатори на събития в .NET framework имат следните параметри: (изпращач на обект, EventArgs args). Тъй като подателят не посочва конкретен тип, трябва да го направите надолу, ако искате да го използвате. Това често е добре на практика, но не изглежда съвсем правилно, защото губите защитата на системата от статичен тип. Но ако внедрите свои собствени събития и не следвате конвенцията на .NET framework за това, можете да използвате правилния тип, така че не е необходимо потенциално преобразуване надолу.

person Scott Langham    schedule 15.02.2009
comment
Малък професионалист за интерфейсите е, че когато имате много събития, които обикновено се събират, не е нужно да ги регистрирате всичките в огромен блок от събития +=s, а регистрирайте само този един интерфейс. Все пак предпочитам събития, разбира се :) - person configurator; 15.02.2009
comment
Един недостатък на събитията е, че те не се активират в домейни на приложения. Ако приложението ви е (или ще бъде) сложно, това ще се превърне в проблем. - person TMN; 27.10.2010
comment
Събитията са добри за рамки, защото обикновено не се променят често и докато хората започнат да ги използват, те трябва да са относително стабилни. Това може да не е така, когато разработвате приложение с бързо променящи се спецификации. Да предположим, че поведението на клас се променя и изисква ново събитие. Ще трябва да проучите всяка употреба на класа. Използвайки интерфейс за наблюдение, компилаторът ще ви информира къде е необходимо внимание, но ако използвате събития, ще имате много по-трудно време. - person Christo; 27.04.2011
comment
Малко известен факт; всъщност не е нужно да декларирате тип делегат за събития. Можете просто да използвате event Action или event Action<string,int> и т.н. - person natli; 18.07.2013
comment
Не съм съгласен с вашата гледна точка срещу интерфейсите, като посочвате, че интерфейсите ви карат да внедрявате всеки метод/реквизит, който е дефиниран в интерфейса, тъй като това очевидно е нарушение на принципа за разделяне на интерфейса. Наличието на добре дефинирани интерфейси с едно предназначение няма да доведе до точно този проблем, който споменахте - person Operatorius; 19.09.2018
comment
@Operatorius Можете да разделите интерфейса във въпроса на интерфейси IFileFound и IFolderFound, но това изглежда като разпространение на интерфейси. Би било по-добре със събития вместо интерфейси за всяко от тях, нали. - person Scott Langham; 20.09.2018
comment
Какво ще кажете за async/await? Събитията изглежда правят това по-трудно - person Joe Phillips; 01.11.2019

Хм, събитията могат да се използват за прилагане на модела на наблюдателя. Всъщност използването на събития може да се разглежда като друга реализация на модела на наблюдателя imho.

person Frederik Gheysels    schedule 15.02.2009
comment
Абсолютно. Това е малко като да питате, трябва ли да внедря модела на итератора или да използвам foreach и IEnumerable? - person Jon Skeet; 15.02.2009
comment
Събитията, стартирани от вашия наблюдател, са естествен начин да уведомите множество потребители за промяна в приложението - person JoshBerke; 15.02.2009
comment
Знам, че това е моделът на наблюдателя; Попитах дали трябва да внедря модела с помощта на интерфейс за обратно извикване или с помощта на събития. - person Roger Lipscombe; 08.12.2009
comment
Вярвам, че използването на събития обикновено е по-добра опция, защото по подразбиране отделя двата компонента и косвено поддържа повече от един източник на събития в бъдеще, без да променя какъвто и да е код. - person Rahul Garg; 12.05.2017

  • Събитията са по-трудни за разпространение чрез верига от обекти, например ако използвате модел FACADE или делегирате работа на друг клас.
  • Трябва да сте много внимателни с отписването от събития, за да позволите на обекта да бъде събиран боклук.
  • Събитията са 2 пъти по-бавни от простото извикване на функция, 3 пъти по-бавно, ако правите нулева проверка при всяко повдигане и копирате делегат на събитие преди нулева проверка и извикване, за да го направите нишково безопасно.

  • Също прочетете MSDN за новия (в 4.0) IObserver<T> интерфейс.

Помислете за този пример:

using System;

namespace Example
{
    //Observer
    public class SomeFacade
    {
        public void DoSomeWork(IObserver notificationObject)
        {
            Worker worker = new Worker(notificationObject);
            worker.DoWork();
        }
    }
    public class Worker
    {
        private readonly IObserver _notificationObject;
        public Worker(IObserver notificationObject)
        {
            _notificationObject = notificationObject;
        }
        public void DoWork()
        {
            //...
            _notificationObject.Progress(100);
            _notificationObject.Done();
        }
    }
    public interface IObserver
    {
        void Done();
        void Progress(int amount);
    }

    //Events
    public class SomeFacadeWithEvents
    {
        public event Action Done;
        public event Action<int> Progress;

        private void RaiseDone()
        {
            if (Done != null) Done();
        }
        private void RaiseProgress(int amount)
        {
            if (Progress != null) Progress(amount);
        }

        public void DoSomeWork()
        {
            WorkerWithEvents worker = new WorkerWithEvents();
            worker.Done += RaiseDone;
            worker.Progress += RaiseProgress;
            worker.DoWork();
            //Also we neede to unsubscribe...
            worker.Done -= RaiseDone;
            worker.Progress -= RaiseProgress;
        }
    }
    public class WorkerWithEvents
    {
        public event Action Done;
        public event Action<int> Progress;

        public void DoWork()
        {
            //...
            Progress(100);
            Done();
        }
    }
}
person Alex Burtsev    schedule 27.03.2012

Плюсове на интерфейсно решение:

  • Ако добавите методи, съществуващите наблюдатели трябва да приложат тези методи. Това означава, че имате по-малък шанс да забравите да свържете съществуващите наблюдатели към нова функционалност. Разбира се, можете да ги приложите като празни методи, което означава, че имате лукса да не правите нищо в отговор на определени „събития“. Но няма да забравиш толкова лесно.
  • Ако използвате изрично внедряване, вие също ще получите грешки на компилатора по друг начин, ако премахнете или промените съществуващите интерфейси, тогава наблюдателите, които ги прилагат, ще спрат да компилират.

Минуси:

  • Трябва да се помисли повече за планирането, тъй като промяна в интерфейса на наблюдателя може да наложи промени в цялото ви решение, което може да изисква различно планиране. Тъй като простото събитие не е задължително, малко или никакъв друг код трябва да се промени, освен ако този друг код не трябва да реагира на събитието.
person Lasse V. Karlsen    schedule 15.02.2009

Някои допълнителни ползи от събитията.

  • Получавате правилното мултикаст поведение безплатно.
  • Ако промените абонатите на събитие в отговор на това събитие, поведението е добре дефинирано
  • Те могат да бъдат интроспектирани (отразени) лесно и последователно
  • Поддръжка на верига от инструменти за събития (просто защото те са идиома в .net)
  • Получавате опцията да използвате асинхронния apis, който предоставя

Можете да постигнете всичко това (с изключение на инструменталната верига) сами, но е изненадващо трудно. Например: Ако използвате членска променлива като List‹> за съхраняване на списъка с наблюдатели. Ако използвате foreach, за да го повторите, тогава всеки опит за добавяне или премахване на абонат в рамките на едно от обратните извиквания на метода OnFoo() ще задейства изключение, освен ако не напишете допълнителен код, за да се справите чисто с него.

person ShuggyCoUk    schedule 15.02.2009
comment
Какви са асинхронните API, които предоставя? - person Joe Phillips; 01.11.2019

Плюсовете са, че събитията са по-„мрежови“. Ако проектирате невизуални компоненти, които могат да бъдат пуснати във формуляр, можете да ги свържете с помощта на дизайнера.

Минусите са, че едно събитие означава само едно събитие - имате нужда от отделно събитие за всяко „нещо“, за което искате да уведомите наблюдателя. Това всъщност няма голямо практическо въздействие, освен че всеки наблюдаван обект ще трябва да държи референция за всеки наблюдател за всяко събитие, раздувайки паметта в случай, че има много наблюдавани обекти (една от причините, поради които са направили различен начин на управление на връзката наблюдател/наблюдавано в WPF).

Във вашия случай бих казал, че няма голяма разлика. Ако наблюдателят обикновено се интересува от всички тези събития, използвайте интерфейс за наблюдател, а не отделни събития.

person U62    schedule 15.02.2009
comment
Е, нищо не ви пречи да имате едно събитие на c#, което да разграничава различни събития чрез дъщерен клас на EventArgs - аз обаче не бих направил това. - person Tamas Czinege; 15.02.2009

Най-добрият начин да решите е следният: кой е по-подходящ за ситуацията. Това може да звучи като глупав или безполезен отговор, но не мисля, че трябва да смятате едното или другото за „правилното“ решение.

Можем да ви дадем стотици съвети. Събитията са най-добри, когато се очаква наблюдателят да слуша за произволни събития. Интерфейсът е най-добър, когато се очаква наблюдателят да посочи всички от даден набор от събития. Събитията са най-добри, когато работите с GUI приложения. Интерфейсите консумират по-малко памет (един указател за множество събития). дяда дяда дяда. Списъкът с плюсове и минуси е нещо, върху което да помислите, но не и окончателен отговор. Това, което наистина трябва да направите, е да опитате и двете в реални приложения и да ги усетите добре. След това можете да изберете този, който е по-подходящ за ситуацията. Научете се да правите формуляри.

Ако трябва да използвате един определящ въпрос, тогава се запитайте кое по-добре описва вашата ситуация: набор от слабо свързани събития, всяко от които може да бъде използвано или пренебрегнато, или набор от тясно свързани събития, които обикновено трябва да бъдат обработени от един наблюдател. Но тогава просто описвам модела на събитието и модела на интерфейса, така че отново съм на изходна позиция: кой отговаря по-добре на ситуацията?

person snarf    schedule 15.02.2009
comment
Ето защо попитах за плюсове/против. Осъзнавам, че няма универсално решение. Това беше нещо, което претеглях и реших, че ще бъде интересен въпрос. - person Roger Lipscombe; 15.02.2009

Java има езикова поддръжка за анонимни интерфейси, така че интерфейсите за обратно извикване са нещото, което трябва да се използва в Java.

C# има поддръжка за анонимни делегати - ламбда - и така събитията са нещото, което трябва да се използва в C#.

person Daniel Earwicker    schedule 15.02.2009

Предимството на интерфейсите е, че те са по-лесни за прилагане на декоратори. Стандартният пример:

subject.RegisterObserver(new LoggingObserver(myRealObserver));

в сравнение с:

subject.AnEvent += (sender, args) => { LogTheEvent(); realEventHandler(sender, args); };

(Аз съм голям фен на модела декоратор).

person Andreas    schedule 27.10.2010
comment
Също толкова лесно е да приложите модела на декоратора върху интерфейс. Просто използвайте инструмент за производителност с вашата IDE, ако цялото писане се превърне в проблем. - person Christo; 27.04.2011

Предпочитам решение за основа на събития поради следните причини

  • Това намалява разходите за влизане. Много по-лесно е да кажете „+= нов EventHandler“, отколкото да внедрите пълноценен интерфейс.
  • Намалява разходите за поддръжка. Ако добавите ново събитие към вашия клас, това е всичко, което трябва да направите. Ако добавите ново събитие към интерфейс, трябва да актуализирате всеки отделен потребител във вашата кодова база. Или дефинирайте изцяло нов интерфейс, който с течение на времето става досаден за потребителите „Да внедря ли IRandomEvent2 или IRandomEvent5?“
  • Събитията позволяват манипулаторите да не са базирани на клас (т.е. статичен метод някъде). Няма функционална причина всички манипулатори на събития да бъдат принудени да бъдат член на екземпляр
  • Групирането на куп събития в интерфейс означава предположение за това как се използват събитията (и това е просто предположение)
  • Интерфейсите не предлагат реално предимство пред необработено събитие.
person JaredPar    schedule 15.02.2009

Ако вашите обекти ще трябва да бъдат сериализирани по някакъв начин, който запазва препратки, като например с NetDataContractSerializer или може би protobuf събития няма да могат преминават границата на сериализация. Тъй като моделът на наблюдателя не разчита на нищо повече от препратки към обекти, той може да работи с този тип сериализация без проблем, ако това е желаното.

Пр. Имате куп бизнес обекти, които се свързват един с друг двупосочно и които трябва да предадете на уеб услуга.

person jpierson    schedule 17.03.2011