Почему отправка событий для отправителя запрещена в C #? - PullRequest
4 голосов
/ 04 сентября 2010

Цитата из: http://msdn.microsoft.com/en-us/library/aa645739(VS.71).aspx

"Вызвать событие можно только из класса, который объявил событие."

Я озадачен, почему существует такое ограничение. Без этого ограничения я мог бы написать класс (один класс), который однажды навсегда управляет отправкой событий для данной категории - например, INotifyPropertyChanged .

С этим ограничением я должен снова и снова копировать и вставлять один и тот же (один и тот же!) Код. Я знаю, что разработчики C # не ценят многократно повторяющийся код (*), но гы ... копируй и вставляй. Насколько это продуктивно?

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

В каждом классе что-то меняется, до конца вашей жизни. Страшно!

Итак, пока я возвращаю свой дополнительный класс отправки (я слишком доверчив) к старому, «хорошему» способу копирования и вставки, вы можете увидеть

что страшного может случиться с возможностью отправлять события для отправителя?

Если вы знаете какие-то хитрости, как обойти это ограничение - не стесняйтесь отвечать также!

(*) с множественным наследованием Я мог бы написать универсальный отправитель один раз навсегда, даже более понятным способом, но C # не имеет множественного наследования

Редактирование

лучший обходной путь до сих пор

Представляем интерфейс

public interface INotifierPropertyChanged : INotifyPropertyChanged
{
    void OnPropertyChanged(string property_name);
}

добавление нового метода расширения Raise для PropertyChangedEventHandler. Затем добавьте класс-посредник для этого нового интерфейса вместо базового INotifyPropertyChanged.

Пока что это минимальный код, который позволяет отправлять вам сообщение от вложенного объекта от имени его владельца (когда владельцу требуется такая логика).

СПАСИБО ВСЕМ ЗА ПОМОЩЬ И ИДЕИ.

Редактировать 1

Гаффа написал:

«Вы не можете заставить что-то произойти, вызывая событие извне»

Это интересный момент, потому что ... я могу. Именно поэтому я и спрашиваю. Посмотри.

Допустим, у вас есть строка класса. Не интересно, правда? Но давайте заполним его классом Invoker, который будет отправлять события каждый раз, когда он меняется.

Сейчас:

class MyClass : INotifyPropertyChanged
{
    public SuperString text { get; set; }
}

Теперь, когда текст изменяется, MyClass изменяется. Поэтому, когда я нахожусь внутри текста, я знаю, что если только у меня есть владелец, он также меняется. Чтобы я мог отправить событие от его имени. И это будет семантически правильно на 100%.

Примечание: мой класс немного умнее - владелец устанавливает, если он хотел бы иметь такую ​​логику.

Редактировать 2

Идея с передачей обработчика события - "2" не будет отображаться.

public class Mediator
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string property_name)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(property_name));
    }

    public void Link(PropertyChangedEventHandler send_through)
    {
        PropertyChanged += new PropertyChangedEventHandler((obj, args) => {
            if (send_through != null)
                send_through(obj, args);
        });
    }

    public void Trigger()
    {
        OnPropertyChanged("hello world");
    }
}
public class Sender
{
    public event PropertyChangedEventHandler PropertyChanged;

    public Sender(Mediator mediator)
    {
        PropertyChanged += Listener1;
        mediator.Link(PropertyChanged);
        PropertyChanged += Listener2;

    }
    public void Listener1(object obj, PropertyChangedEventArgs args)
    {
        Console.WriteLine("1");
    }
    public void Listener2(object obj, PropertyChangedEventArgs args)
    {
        Console.WriteLine("2");
    }
}

    static void Main(string[] args)
    {
        var mediator = new Mediator();
        var sender = new Sender(mediator);
        mediator.Trigger();

        Console.WriteLine("EOT");
        Console.ReadLine();
    }

Редактировать 3

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

Редактировать 4

Небольшой пример моего кода (конечное использование), Дэн, пожалуйста, посмотрите:

public class ExperimentManager : INotifierPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string property_name)
    {
        PropertyChanged.Raise(this, property_name);
    }


    public enum Properties
    {
        NetworkFileName,
        ...
    }

    public NotifierChangedManager<string> NetworkFileNameNotifier;
    ...

    public string NetworkFileName 
    { 
         get { return NetworkFileNameNotifier.Value; } 
         set { NetworkFileNameNotifier.Value = value; } 
    }

    public ExperimentManager()
    {
        NetworkFileNameNotifier = 
            NotifierChangedManager<string>.CreateAs(this, Properties.NetworkFileName.ToString());
        ... 
    }

Ответы [ 6 ]

5 голосов
/ 04 сентября 2010

Подумайте об этом на секунду, прежде чем отправиться на разгул.Если какой-либо метод может вызвать событие для какого-либо объекта, разве это не нарушит инкапсуляцию и также может сбить с толку?Смысл событий заключается в том, что экземпляры класса с этим событием могут уведомлять другие объекты о том, что произошло какое-то событие.Событие имеет от этого класса, а не от любого другого.В противном случае события становятся бессмысленными, потому что любой может в любой момент вызвать любое событие на любом объекте, что означает, что при возникновении события вы точно не знаете, произошло ли это на самом деле из-за действия, которое оно представляет, или просто потому, что какой-то сторонний класс решилчтобы повеселиться.

Тем не менее, если вы хотите иметь возможность разрешать какие-то события-посредники для отправки вам событий, просто откройте объявление события с помощью обработчиков добавления и удаления.Тогда вы могли бы сделать что-то вроде этого:

public event PropertyChangedEventHandler PropertyChanged {
    add {
        propertyChangedHelper.PropertyChanged += value;
    }
    remove {
        propertyChangedHelper.PropertyChanged -= value;
    }
}

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

4 голосов
/ 04 сентября 2010

Если кто-либо может поднять любое событие, мы сталкиваемся с этой проблемой:

@ Рекс М: всем привет, @macias только что поднял руку!

@ macias: нет, не знаю.

@ все: слишком поздно! @Rex M сказал, что ты это сделал, и мы все приняли меры, веря в это.

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

2 голосов
/ 04 сентября 2010

Я думаю, вы неправильно понимаете ограничение. То, что это пытается сказать, - то, что только класс, который объявил событие, должен фактически вызвать его. Это отличается от написания статического вспомогательного класса для инкапсуляции фактической реализации обработчика событий.

Класс A, который является внешним по отношению к классу, который объявляет событие B, не должен вызывать B для непосредственного вызова этого события. Единственный способ заставить A заставить B вызвать событие - это выполнить какое-то действие с B, которое в результате выполнения этого действия вызывает событие.

В случае INotifyPropertyChanged задан следующий класс:

public class Test : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;

   private string name;

   public string Name
   {
      get { return this.name; }
      set { this.name = value; OnNotifyPropertyChanged("Name"); }
   }

   protected virtual void OnPropertyChanged(string name)
   {
       PropertyChangedEventHandler  temp = PropertyChanged;
       if (temp!= null)
       {
          temp(this, new PropertyChangedEventArgs(name));
       }
   }
}

Единственный способ для класса, потребляющего Test, заставить Test вызвать событие PropertyChanged, это установить свойство Name:

public void TestMethod()
{
    Test t = new Test();
    t.Name = "Hello"; // This causes Test to raise the PropertyChanged event
}

Вы не хотели бы, чтобы код был похож на:

public void TestMethod()
{
    Test t = new Test();
    t.Name = "Hello";
    t.OnPropertyChanged("Name");
}

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

/// <summary>
/// Provides static methods for event handling. 
/// </summary>
public static class EventManager
{
    /// <summary>
    /// Raises the event specified by <paramref name="handler"/>.
    /// </summary>
    /// <typeparam name="TEventArgs">
    /// The type of the <see cref="EventArgs"/>
    /// </typeparam>
    /// <param name="sender">
    /// The source of the event.
    /// </param>
    /// <param name="handler">
    /// The <see cref="EventHandler{TEventArgs}"/> which 
    /// should be called.
    /// </param>
    /// <param name="e">
    /// An <see cref="EventArgs"/> that contains the event data.
    /// </param>
    public static void OnEvent<TEventArgs>(object sender, EventHandler<TEventArgs> handler, TEventArgs e)
         where TEventArgs : EventArgs
    {
        // Make a temporary copy of the event to avoid possibility of
        // a race condition if the last subscriber unsubscribes
        // immediately after the null check and before the event is raised.
        EventHandler<TEventArgs> tempHandler = handler;

        // Event will be null if there are no subscribers
        if (tempHandler != null)
        {
            tempHandler(sender, e);
        }
    }

    /// <summary>
    /// Raises the event specified by <paramref name="handler"/>.
    /// </summary>
    /// <param name="sender">
    /// The source of the event.
    /// </param>
    /// <param name="handler">
    /// The <see cref="EventHandler"/> which should be called.
    /// </param>
    public static void OnEvent(object sender, EventHandler handler)
    {
        OnEvent(sender, handler, EventArgs.Empty);
    }

    /// <summary>
    /// Raises the event specified by <paramref name="handler"/>.
    /// </summary>
    /// <param name="sender">
    /// The source of the event.
    /// </param>
    /// <param name="handler">
    /// The <see cref="EventHandler"/> which should be called.
    /// </param>
    /// <param name="e">
    /// An <see cref="EventArgs"/> that contains the event data.
    /// </param>
    public static void OnEvent(object sender, EventHandler handler, EventArgs e)
    {
        // Make a temporary copy of the event to avoid possibility of
        // a race condition if the last subscriber unsubscribes
        // immediately after the null check and before the event is raised.
        EventHandler tempHandler = handler;

        // Event will be null if there are no subscribers
        if (tempHandler != null)
        {
            tempHandler(sender, e);
        }
    }
}

Совершенно законно изменить Test следующим образом:

public class Test : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;

   private string name;

   public string Name
   {
      get { return this.name; }
      set { this.name = value; OnNotifyPropertyChanged("Name"); }
   }

   protected virtual void OnPropertyChanged(string name)
   {
       EventHamanger.OnEvent(this, PropertyChanged, new PropertyChangedEventArgs(name));
   }
}
1 голос
/ 04 сентября 2010

Обновление

Хорошо, прежде чем мы пойдем дальше, мы определенно должны прояснить определенный момент.

Похоже, вы хотите, чтобы это произошло:

class TypeWithEvents
{
    public event PropertyChangedEventHandler PropertyChanged;

    // You want the set method to automatically
    // raise the PropertyChanged event via some
    // special code in the EventRaisingType class?
    public EventRaisingType Property { get; set; }
}

Вы действительно хотите иметь возможность писать свой код именно так?Это действительно полностью невозможно - по крайней мере, в .NET (по крайней мере, до тех пор, пока команда C # не придумает какой-нибудь необычный новый синтаксический сахар специально для интерфейса INotifyPropertyChanged, который, я считаю,фактически обсуждался) - поскольку объект не имеет понятия «переменные, которые были назначены мне».На самом деле нет никакого способа представить переменную , используя объект вообще (я полагаю, что выражения LINQ - это способ, но это совершенно другой предмет).Это работает только наоборот.Допустим, у вас есть:

Person x = new Person("Bob");
x = new Person("Sam");

Знает ли «Боб», что x только что назначен на «Сэма»? Абсолютно нет : переменная x просто указала на «Боб», она никогда не была «Боб»;поэтому «Боб» не знает и не заботится о том, что происходит с x.

. Таким образом, объект не может надеяться на выполнение какого-либо действия, основанного на изменении переменной, указывающей на него, на точкуна что-то другое.Это было бы так, как если бы вы написали мое имя и адрес на конверте, а затем стерли его и написали имя другого человека, и я каким-то волшебным образом знал, что @ macias просто изменил адрес на конверте с моего на чужой!

Конечно, вы можете * изменить свойство так, чтобы его методы get и set модифицировали другое свойство частного member и свяжите ваши события с событием, предоставленным этим участником (это, по сути, siride предложил ).В этом сценарии было бы разумно желать ту функциональность, о которой вы спрашиваете.Это сценарий, который я имею в виду в своем первоначальном ответе:


Оригинальный ответ

Я бы не сказал, что то, о чем вы просите, просто плосконе так, как полагают некоторые другие.Очевидно, что может быть полезной для разрешения частному члену класса вызывать одно из событий этого класса, например, в описанном вами сценарии.И хотя идея Саураба хорошая, ясно, что она не всегда может быть применена, так как в C # отсутствует множественное наследование *.

, что подводит меня к моей точке зрения.Почему не C # допускает множественное наследование?Я знаю, что это может показаться не по теме, но ответы на этот и тот же вопрос одинаковы.Дело не в том, что это незаконно, потому что это «никогда» не имеет смысла;это незаконно, потому что есть просто больше минусов, чем плюсов. Многократное наследование очень трудно понять правильно.Точно так же поведение, которое вы описываете, было бы очень легко злоупотреблять.

То есть, да, общий случай, описанный Рексом , дает довольно хороший аргумент против объектов, поднимающих другие объекты.' События.Сценарий , который вы описали , с другой стороны - постоянное повторение стандартного кода - похоже, что-то вроде в пользу такого поведения.Вопрос в том, какому вопросу следует придать больший вес?

Допустим, разработчики .NET решили разрешить это и просто надеются, что разработчики не злоупотребят этим.Почти наверняка будет много больше неработающего кода там, где разработчик класса X не ожидал, что событие E будет вызвано классом Y, в другой сборке.Но это так, и состояние объекта X становится недействительным, и тонкие ошибки появляются везде.

А как насчет противоположного сценария?Что если они запретят это?Теперь, конечно, мы просто рассматриваем реальность, потому что это является случаем.Но какой здесь огромный недостаток?Вы должны скопировать и вставить один и тот же код в кучу мест.Да, это раздражает;но также есть способы смягчить это (например, идея базового класса Саураба).И возбуждение событий строго определяется типом объявления, всегда , что дает нам гораздо большую уверенность в поведении наших программ.

Итак:

EVENT POLICY                  | PROS                | CONS
------------------------------+---------------------+-------------------------
Allowing any object to raise  | Less typing in      | Far less control over
another object's event        | certain cases       | class behavior, abun-
                              |                     | dance of unexpected 
                              |                     | scenarios, proliferation
                              |                     | of subtle bugs
                              |                     |
------------------------------------------------------------------------------
Restricting events to be only | Much better control | More typing required
raised by the declaring type  | of class behavior,  | in some cases
                              | no unexpected       |
                              | scenarios, signifi- |
                              | cant decrease in    |
                              | bug count           |

Представьте, что вы несете ответственность за решение о том, какую политику событий реализовать для .NET.Какой из них вы бы выбрали?

* Я говорю «C #», а не «.NET», потому что на самом деле я не уверен, является ли запрет на множественное наследование CLR или просто C #,Кто-нибудь знает?

1 голос
/ 04 сентября 2010

События предназначены для уведомления о том, что что-то произошло.Класс, в котором объявлено событие, заботится о том, чтобы вызвать событие в нужное время.

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

Таким образом, позволить запускать события извне класса можно почти только неправильно.Если по какой-то причине было бы полезно инициировать событие извне, класс может легко предоставить метод, который позволяет это.

1 голос
/ 04 сентября 2010

Прежде всего я должен сказать, что события на самом деле предназначены для вызова изнутри объекта, только когда к нему присоединен какой-либо внешний обработчик событий.

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

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

Если вы не хотите делать подобные вещи в каждом классе, вы можете легко создать EventInvoker, который определяет каждый из них, и в его конструкторе вы передаете делегат.

public class EventInvoker
{
   public EventInvoker(EventHandler<EventArgs> eventargs)
   {
      //set the delegate. 
   } 

   public void InvokeEvent()
   {
        // Invoke the event. 
   }
}

Таким образом, в основном вы создаете прокси-класс для каждого из этих методов, и создание этого универсального метода позволит вам вызывать события для любого события. Таким образом, вы можете легко избежать вызова этих свойств каждый раз.

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