开发者

Why sending events for a sender is forbidden in C#?

开发者 https://www.devze.com 2023-01-14 05:25 出处:网络
Quote from: http://msdn.microsoft.com/en-us/library/aa645739(VS.71).aspx "Invoking an event can only be done from within the class that declared the event."

Quote from: http://msdn.microsoft.com/en-us/library/aa645739(VS.71).aspx

"Invoking an event can only be done from within the class that declared the event."

I am puzzled why there is such restriction. Without this limitation I would be able to write a class (one class) which once for good manages sending the events for a given category -- like INotifyPropertyChanged.

With this limitation I have to copy and paste the same (the same!) code all over again. I know that designers of C# don't value code reuse too much (*), 开发者_如何学Gobut gee... copy&paste. How productive is that?

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

In every class changing something, to the end of your life. Scary!

So, while I am reverting my extra sending class (I am too gullible) to old, "good" copy&paste way, can you see

what terrible could happen with the ability to send events for a sender?

If you know any tricks how to avoid this limitation -- don't hesitate to answer as well!

(*) with multi inheritance I could write universal sender once for good in even clearer manner, but C# does not have multi inheritance

Edits

The best workaround so far

Introducing interface

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

adding new extension method Raise for PropertyChangedEventHandler. Then adding mediator class for this new interface instead of basic INotifyPropertyChanged.

So far it is minimal code that let's send you message from nested object in behalf of its owner (when owner required such logic).

THANK YOU ALL FOR THE HELP AND IDEAS.

Edit 1

Guffa wrote:

"You couldn't cause something to happen by triggering an event from the outside,"

It is interesting point, because... I can. It is exactly why I am asking. Take a look.

Let's say you have class string. Not interesting, right? But let's pack it with Invoker class, which send events every time it changed.

Now:

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

Now, when text is changed MyClass is changed. So when I am inside text I know, that if only I have owner, it is changed as well. So I could send event on its behalf. And it would be semantically 100% correct.

Remark: my class is just a bit smarter -- owner sets if it would like to have such logic.

Edit 2

Idea with passing the event handler -- "2" won't be displayed.

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();
    }

Edit 3

As an comment to all argument about misuse of direct event invoking -- misuse is of course still possible. All it takes is implementing the described above workaround.

Edit 4

Small sample of my code (end use), Dan please take a look:

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());
        ... 
    }


Think about it for a second before going off on a rant. If any method could invoke an event on any object, would that not break encapsulation and also be confusing? The point of events is so that instances of the class with the event can notify other objects that some event has occurred. The event has to come from that class and not from any other. Otherwise, events become meaningless because anyone can trigger any event on any object at any time meaning that when an event fires, you don't know for sure if it's really because the action it represents took place, or just because some 3rd party class decided to have some fun.

That said, if you want to be able to allow some sort of mediator class send events for you, just open up the event declaration with the add and remove handlers. Then you could do something like this:

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

And then the propertyChangedHelper variable can be an object of some sort that'll actually fire the event for the outer class. Yes, you still have to write the add and remove handlers, but it's fairly minimal and then you can use a shared implementation of whatever complexity you want.


Allowing anyone to raise any event puts us in this problem:

@Rex M: hey everybody, @macias just raised his hand!

@macias: no, I didn't.

@everyone: too late! @Rex M said you did, and we all took action believing it.

This model is to protect you from writing applications that can easily have invalid state, which is one of the most common sources of bugs.


I think you're misunderstanding the restriction. What this is trying to say is that only the class which declared the event should actually cause it to be raised. This is different than writing a static helper class to encapsulate the actual event handler implementation.

A class A that is external to the class which declares the event B should not be able to cause B to raise that event directly. The only way A should have to cause B to raise the event is to perform some action on B which, as a result of performing that action raises the event.

In the case of INotifyPropertyChanged, given the following class:

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));
       }
   }
}

The only way for a class consuming Test to cause Test to raise the PropertyChanged event is by setting the Name property:

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

You would not want code that looked like:

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

All of that being said, it is perfectly acceptable to write a helper class which encapsualtes the actual event handler implementation. For example, given the following EventManager class:

/// <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);
        }
    }
}

It's perfectly legal to change Test as follows:

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));
   }
}


First of all I must say, events are actually meant to be invoked from within the object only when any external eventhandler is attached to it.

So basically, the event gives you a callback from an object and gives you a chance to set the handler to it so that when the event occurs it automatically calls the method.

This is similar of sending a value of variable to a member. You can also define a delegate and send the handler the same way. So basically its the delegate that is assigned the function body and eventually when the class invokes the event, it will call the method.

If you dont want to do stuffs like this on every class, you can easily create an EventInvoker which defines each of them, and in the constructor of it you pass the delegate.

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

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

So basically you create a proxy class on each of those methods and making this generic will let you invoke events for any event. This way you can easily avoid making call to those properties every time.


Events are intended for being notified that something has happened. The class where event is declared takes care of triggering the event at the right time.

You couldn't cause something to happen by triggering an event from the outside, you would only cause every event subscriber to think that it had happened. The correct way to make something happen is to actually make it happen, not to make it look like it happened.

So, allowing events to be triggered from outside the class can almost only be misused. On the off chance that triggering an event from the outside would be useful for some reason, the class can easily provide a method that allows it.


Update

OK, before we go any further, we definitely need to clarify a certain point.

You seem to want this to happen:

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; }
}

Do you really want to be able to write your code just like this? This is really totally impossible -- at least in .NET (at least until the C# team comes up with some fancy new syntactic sugar specifically for the INotifyPropertyChanged interface, which I believe has actually been discussed) -- as an object has no notion of "the variables that have been assigned to me." In fact there is really no way to represent a variable using an object at all (I suppose LINQ expressions is a way, actually, but that's a totally different subject). It only works the opposite way. So let's say you have:

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

Does "Bob" know x just got assigned to "Sam"? Absolutely not: the variable x just pointed to "Bob", it never was "Bob"; so "Bob" doesn't know or care one lick about what happens with x.

Thus an object couldn't possibly hope to perform some action based on when a variable pointing to it gets changed to point at something else. It would be as if you wrote my name and address on an envelope, and then you erased it and wrote somebody else's name, and I somehow magically knew, @macias just changed the address on an envelope from mine to someone else's!

Of course, what you can do is modify a property so that its get and set methods modify a different property of a private member, and link your events to an event supplied by that member (this is essentially what siride has suggested). In this scenario it would be kind of reasonable to desire the functionality you're asking about. This is the scenario that I have in mind in my original answer, which follows.


Original Answer

I wouldn't say that what you're asking for is just flat-out wrong, as some others seem to be suggesting. Obviously there could be benefits to allowing a private member of a class to raise one of that class's events, such as in the scenario you've described. And while saurabh's idea is a good one, clearly, it cannot always be applied since C# lacks multiple inheritance*.

Which gets me to my point. Why doesn't C# allow multiple inheritance? I know this might seem off-topic, but the answers to this and that question are the same. It isn't that it's illegal because it would "never" make sense; it's illegal because there are simply more cons than pros. Multiple inheritance is very hard to get right. Similarly, the behavior you are describing would be very easy to abuse.

That is, yes, the general case Rex has described makes a pretty good argument against objects raising other objects' events. The scenario you've described, on the other hand -- the constant repetition of boilerplate code -- seems to make something of a case in favor of this behavior. The question is: which consideration should be given greater weight?

Let's say the .NET designers decided to allow this, and simply hope that developers would not abuse it. There would almost certainly be a lot more broken code out there where the designer of class X did not anticipate that event E would be raised by class Y, way off in another assembly. But it does, and the X object's state becomes invalid, and subtle bugs creep in everywhere.

What about the opposite scenario? What if they disallowed it? Now of course we're just considering reality, because this is the case. But what is the huge downside here? You have to copy and paste the same code in a bunch of places. Yes, it's annoying; but also, there are ways to mitigate this (such as saurabh's base class idea). And the raising of events is strictly defined by the declaring type, always, which gives us much greater certainty about the behavior of our programs.

So:

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           |

Imagine you're responsible for deciding which event policy to implement for .NET. Which one would you choose?

*I say "C#" rather than ".NET" because I'm actually not sure if the prohibition on multiple inheritance is a CLR thing, or just a C# thing. Anybody happen to know?

0

精彩评论

暂无评论...
验证码 换一张
取 消