为什么在C#中禁止为发件人发送事件?

时间:2010-09-04 15:12:58

标签: c# events

引用来自: http://msdn.microsoft.com/en-us/library/aa645739(VS.71).aspx

“调用事件只能在声明事件的类中完成。”

我很困惑为什么会有这样的限制。如果没有这个限制,我就可以编写一个类(一个类),这个类可以管理为给定类别发送事件 - 比如 INotifyPropertyChanged

有了这个限制,我必须重新复制并粘贴相同(相同!)的代码。我知道C#的设计者并不重视代码重用太多(*),但是gee ...复制和粘贴。效率如何?

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

为PropertyChangedEventHandler添加新的扩展方法Raise。然后为这个新接口添加mediator类,而不是基本的INotifyPropertyChanged。

到目前为止,它是一个最小的代码,让我们代表它的所有者从嵌套对象发送消息(当所有者需要这样的逻辑时)。

感谢所有人的帮助和想法。

编辑1

Guffa写道:

“通过从外部触发事件,您无法做出任何事情,”

这很有意思,因为......我可以。这正是我要问的原因。看一看。

假设您有类字符串。不好意思吧?但是让我们用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

我的代码的小样本(最终用途),Dan请看一下:

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 个答案:

答案 0 :(得分:5)

在咆哮之前想一想。如果任何方法可以在任何对象上调用事件,那么这不会破坏封装并且也会引起混淆吗?事件的关键是,具有事件的类的实例可以通知其他对象已发生某些事件。事件来自该类,而不是来自任何其他类。否则,事件变得毫无意义,因为任何人都可以随时触发任何对象上的任何事件,这意味着当事件触发时,您不确定它是否真的是因为它所代表的行为发生了,或者仅仅是因为某些第三方阶级决定了有一些乐趣。

也就是说,如果您希望能够允许某种类型的中介类发送事件,只需使用添加和删除处理程序打开事件声明。然后你可以做这样的事情:

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

然后propertyChangedHelper变量可以是某种类型的对象,它实际上会为外部类触发事件。是的,您仍然需要编写添加和删除处理程序,但它相当小,然后您可以使用任何复杂的共享实现。

答案 1 :(得分:4)

允许任何人提出任何事件都会让我们陷入这个问题:

  

@Rex M:嘿大家,@ macias刚举起手来!

     

@macias:不,我没有。

     

@everyone:太晚了! @Rex M说你做了,我们都采取行动相信它。

此模型旨在保护您免受编写容易出现无效状态的应用程序的影响,这是最常见的错误来源之一。

答案 2 :(得分:2)

我认为你误解了限制。这是试图说只有声明事件的类实际上应该引发它。这与编写静态助手类来封装实际的事件处理程序实现不同。

声明事件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

答案 3 :(得分:1)

首先,我必须说,事件实际上意味着只有在附加任何外部事件处理程序时才从对象内部调用。

基本上,该事件为您提供了一个来自对象的回调,并让您有机会为其设置处理程序,以便在事件发生时自动调用该方法。

这类似于向成员发送变量值。您还可以定义委托并以相同的方式发送处理程序。所以基本上它是分配了函数体的委托,最终当类调用事件时,它将调用该方法。

如果你不想在每个类上做这样的事情,你可以轻松创建一个定义每个类的EventInvoker,并在它的构造函数中传递委托。

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

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

所以基本上你在每个方法上创建一个代理类,并使这个泛型允许你为任何事件调用事件。这样您就可以轻松避免每次调用这些属性。

答案 4 :(得分:1)

事件旨在通知发生了某些事情。声明事件的类负责在适当的时间触发事件。

您无法通过从外部触发事件来导致某些事情发生,您只会让每个事件订阅者都认为它已经发生。让事情发生的正确方法是实际实现它,而不是让它看起来像是发生过。

因此,允许从类外部触发事件几乎只能被滥用。关于从外部触发事件的机会由于某种原因是有用的,该类可以很容易地提供允许它的方法。

答案 5 :(得分:1)

更新

好的,在我们继续前进之前,我们肯定需要澄清一点。

您似乎希望这种情况发生:

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

“Bob”知道x刚被分配到“Sam”吗? 绝对不是:变量x只是指向到“Bob”,它永远 “Bob”;所以“鲍勃”不知道或关心x会发生什么。

因此,对象不可能希望根据指向它的变量何时更改为指向其他内容来执行某些操作。就好像你在一个信封上写下了我的名字和地址,然后你擦掉了它并写了别人的名字,我不知何故神奇地知道, @macias只是把信封上的地址从我改为别人的!

当然,您可以做的是修改属性,以便其getset方法修改私有的不同属性成员,并将您的事件链接到该成员提供的事件(这基本上是siride has suggested)。在这种情况下,希望您询问的功能是合理的种类。这是我在原始答案中所考虑的情景,后面是。


原始答案

我不会说你所要求的只是错误,正如其他人似乎在暗示的那样。显然,可以允许类的私有成员引发该类事件之一,例如在您描述的场景中。虽然saurabh's idea是一个很好的,但显然,它并不总是适用,因为C#缺少多重继承*。

这让我明白了。为什么 C#允许多重继承?我知道这可能看似偏离主题,但这个和那个问题的答案是一样的。这不是非法的,因为它“永远”没有意义;这是非法的,因为它只是比专业人士更多的缺点。 多重继承很难做到。同样,您描述的行为也很容易被滥用。

也就是说,the general case Rex has described对于引发其他对象事件的对象提出了一个很好的论据。另一方面,描述的场景 - 样板代码的不断重复 - 似乎使某些案例支持这种行为。问题是:应该考虑哪些考虑因素?

让我们说.NET设计者决定允许这样做,并希望开发人员不会滥用它。几乎肯定会有一个很多更多破坏的代码,其中类X的设计者没有预料到类E会引发事件Y,在另一个集会中离开。但确实如此,X对象的状态变得无效,并且细微的错误遍布各处。

相反的情况怎么样?如果他们不允许怎么办?当然,我们当然只是在考虑现实,因为的情况。但这里的巨大缺点是什么?您必须在一堆地方复制并粘贴相同的代码。是的,这很烦人;而且,有一些方法可以缓解这种情况(比如saurabh的基类思想)。事件的提出由声明类型严格定义,始终,这使我们对程序的行为有了更大的确定性。

所以:

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#的东西。有人碰巧知道吗?