代码之家  ›  专栏  ›  技术社区  ›  greenoldman

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

  •  4
  • greenoldman  · 技术社区  · 14 年前

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

    “只能从声明事件的类中调用事件。”

    我不明白为什么会有这样的限制。如果没有这个限制,我将能够编写一个类(一个类),该类一次性地管理发送给定类别的事件,比如 InotifyProperty已更改 .

    由于这个限制,我必须复制和粘贴相同的(相同!)重新编码。我知道C#的设计者不太重视代码重用(*),但是天啊。。。复制和粘贴。这有多有效?

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

    在每一堂课上,改变一些东西,直到你生命的尽头。吓人的!

    所以,当我把我额外的发送类(我太容易受骗)恢复到旧的“好”复制粘贴方式时,你能看到吗

    如果能够为发送者发送事件,会发生什么可怕的事情?

    如果你知道如何避免这个限制的任何技巧——也不要犹豫回答!

    (*)使用多重继承,我可以以更清晰的方式一次性编写universal sender,但C#没有多重继承

    迄今为止最好的解决方法

    引入接口

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

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

    作为对所有关于滥用直接事件调用的争论的评论——滥用当然仍然是可能的。所需要的就是实现上述解决方法。

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

    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 回复  |  直到 4 年前
        1
  •  5
  •   siride    14 年前

    在大喊大叫之前先想一想。如果有任何方法可以调用任何对象上的事件,这会不会破坏封装,也会造成混淆?事件的意义在于,具有该事件的类的实例可以通知其他对象发生了某个事件。事件

    也就是说,如果您想允许某种中介类为您发送事件,只需使用add和remove处理程序打开事件声明。然后你可以这样做:

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

        2
  •  4
  •   Rex M    14 年前

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

    @雷克斯:嘿,各位,马西亚斯刚刚举起手来!

    @马西亚斯:不,我没有。

    @大家:太晚了!@雷克斯说是你干的,我们都相信。

    这个模型是为了保护你不去写那些很容易出现无效状态的应用程序,这是最常见的bug来源之一。

        3
  •  2
  •   Scott Dorman    14 年前

    我想你误解了限制。这试图说明的是,只有声明了事件的类才应该真正引发它。这不同于编写一个静态助手类来封装实际的事件处理程序实现。

    A 它位于声明事件的类的外部 B 应该不会引起 直接提出那个事件。唯一的办法 A 引发事件就是对 执行该操作的结果会引发事件。

    如果是 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 使 试验 提高 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);
            }
        }
    }
    

    改变是完全合法的

    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));
       }
    }
    
        4
  •  1
  •   abhishek    14 年前

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

    因此,基本上,事件会给您一个对象的回调,并给您一个机会将处理程序设置为该对象,以便在事件发生时自动调用该方法。

    如果您不想在每个类上都执行这样的操作,那么可以轻松地创建一个EventInvoker来定义每个类,并在它的构造函数中传递委托。

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

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

        5
  •  1
  •   Guffa    14 年前

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

        6
  •  1
  •   Community Mofi    7 年前

    更新

    好的,在我们进一步讨论之前,我们肯定需要澄清一点。

    你似乎希望这样:

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

    你真的想这样写你的代码吗?这真是太棒了 完全地 不可能的 INotifyPropertyChanged 接口,我相信已经讨论过了 没有“分配给我的变量”的概念。事实上,实在没有办法表示 变量 使用一个对象(实际上,我认为LINQ表达式是一种方式,但这是一个完全不同的主题)。它只起相反的作用。假设你有:

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

    “鲍勃”知道吗 x 刚被分配到“山姆”? :变量 只是 指出 对“鲍勃”来说,从来没有 “鲍勃”;所以“鲍勃”一点也不知道也不在乎发生了什么

    @马西亚斯刚把信封上的地址从我的改成了别人的!

    当然,你说什么 get set 不同的 私人财产 成员 siride has suggested ). 在这种情况下 合理的要求你所要求的功能。这就是我在接下来的最初答案中所想的情景。


    能够 允许类的私有成员引发该类的某个事件可能有好处,例如在您描述的场景中。而当 saurabh's idea 是一个很好的方法,显然,它不能总是应用,因为C#缺少多重继承*。

    这让我明白了我的意思。为什么? 是否允许多重继承?我知道这看起来有点离题,但这个问题和那个问题的答案是一样的。这并不是说它是非法的,因为它“永远”没有意义;它是非法的,因为它的利大于弊。

    就是说,是的, the general case Rex has described 对引发其他对象事件的对象进行了很好的论证。情景 另一方面,描述——样板代码的不断重复——似乎有点道理 这种行为。问题是:哪种考虑应该给予更大的重视?

    假设.NET设计师决定允许这样做,只是希望开发人员不要滥用它。几乎可以肯定会有 许多 X 没有预料到那件事 E 将由班级抚养 Y ,在另一个装配中。但确实如此,而且

    相反的情况呢?如果他们不允许呢?当然我们只是在考虑现实,因为 这个案子。但这里的巨大缺点是什么?你必须在一堆地方复制和粘贴相同的代码。是的,这很烦人;但是,也有一些方法可以缓解这种情况(比如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           |