代表和活动之间有什么区别?

时间:2008-08-26 23:06:32

标签: c# events delegates glossary

代表和活动之间有什么区别?两者都不能保存对可以执行的函数的引用吗?

13 个答案:

答案 0 :(得分:259)

事件声明在委托实例上添加了一层抽象和保护。此保护可防止委托的客户端重置委托及其调用列表,并且只允许在调用列表中添加或删除目标。

答案 1 :(得分:92)

除了语法和操作属性之外,还有语义差异。

代表是概念上的功能模板;也就是说,他们表达了一个函数必须遵守的契约,以便被认为是代表的“类型”。

事件代表......好吧,事件。它们的目的是在某些事情发生时提醒某人,是的,它们遵循代理定义,但它们不是一回事。

即使它们完全相同(语法和IL代码),仍然会保留语义差异。一般来说,我更喜欢为两个不同的概念设置两个不同的名称,即使它们以相同的方式实现(这并不意味着我希望两次使用相同的代码)。

答案 2 :(得分:92)

要理解这些差异,你可以看看这两个例子

代表示例(在这种情况下,是一个Action - 这是一种不返回值的委托)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

要使用委托,您应该执行以下操作:

Animal animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();

此代码效果很好,但您可能会遇到一些弱点。

例如,如果我写这个:

animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;

使用最后一行代码,我已经覆盖了以前的行为,只丢失了一个+(我使用=代替+=

另一个弱点是,每个使用Animal课程的班级都可以RaiseEvent加注animal.RaiseEvent()

为避免这些弱点,您可以在c#中使用events

您的Animal类将以这种方式改变:

public class ArgsSpecial : EventArgs
{
    public ArgsSpecial (string val)
    {
        Operation=val;
    }

    public string Operation {get; set;}
} 

public class Animal
{
    // Empty delegate. In this way you are sure that value is always != null 
    // because no one outside of the class can change it.
    public event EventHandler<ArgsSpecial> Run = delegate{} 

    public void RaiseEvent()
    {  
         Run(this, new ArgsSpecial("Run faster"));
    }
}

调用事件

 Animal animal= new Animal();
 animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
 animal.RaiseEvent();

的差异:

  1. 您不是使用公共属性,而是使用公共字段(使用事件,编译器会保护您的字段免受不必要的访问)
  2. 不能直接分配事件。在这种情况下,它不会引起我在覆盖行为时显示的先前错误。
  3. 班上没有人可以举起活动。
  4. 事件可以包含在接口声明中,而字段不能
  5. 注意:

    EventHandler被声明为以下委托:

    public delegate void EventHandler (object sender, EventArgs e)
    

    它需要一个发送者(Object类型)和事件参数。如果发件人来自静态方法,则该发件人为空。

    此示例使用EventHandler<ArgsSpecial>,也可以使用EventHandler编写。

    有关EventHandler

    的文档,请参阅here

答案 3 :(得分:34)

这是另一个很好的链接。 http://csharpindepth.com/Articles/Chapter2/Events.aspx

简而言之,从文章中删除 - 事件是对代表的封装。

引用文章:

  

假设事件在C#/ .NET中不作为概念存在。另一个班级如何订阅活动?三个选项:

     
      
  1. 公共委托变量

  2.   
  3. 由属性

  4. 支持的委托变量   
  5. 使用AddXXXHandler和RemoveXXXHandler方法的委托变量

  6.         

    选项1显然很可怕,因为我们厌恶公共变量的所有正常原因。

         

    选项2略胜一筹,但允许订阅者有效地互相覆盖 - 编写someInstance.MyEvent = eventHandler会很容易;它将替换任何现有的事件处理程序,而不是添加新的事件处理程序。此外,您仍需要编写属性。

         

    选项3基本上是事件给你的东西,但有一个保证的约定(由编译器生成并由IL中的额外标志支持)和一个“免费”实现,如果你对字段式事件的语义感到满意给你。订阅和取消订阅事件是封装的,不允许任意访问事件处理程序列表,语言可以通过为声明和订阅提供语法来简化操作。

答案 4 :(得分:7)

注意:如果您有权访问C# 5.0 Unleashed,请阅读标题为“事件”的第18章中“代理人明确使用的限制”,以便更好地理解两者之间的差异。


总有一个简单,具体的例子可以帮助我。所以这是社区的一个。首先,我将展示如何单独使用代表来完成事件为我们所做的事情。然后我展示了同一解决方案如何与EventHandler的实例一起使用。然后我解释为什么我们不想做我在第一个例子中解释的内容。这篇文章的灵感来自John Skeet的an article

示例1:使用公共委托

假设我有一个带有单个下拉框的WinForms应用程序。下拉列表绑定到List<Person>。 Person具有Id,Name,NickName,HairColor的属性。在主窗体上是一个自定义用户控件,显示该人员的属性。当有人在下拉列表中选择某个人时,用户控件中的标签会更新,以显示所选人员的属性。

enter image description here

这是如何工作的。我们有三个文件可以帮助我们将它们放在一起:

  • Mediator.cs - 静态类包含代理
  • Form1.cs - 主要表格
  • DetailView.cs - 用户控件显示所有细节

以下是每个类的相关代码:

class Mediator
{
    public delegate void PersonChangedDelegate(Person p); //delegate type definition
    public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this.
    public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes.
    {
        if (PersonChangedDel != null)
        {
            PersonChangedDel(p);
        }
    }
}

以下是我们的用户控件:

public partial class DetailView : UserControl
{
    public DetailView()
    {
        InitializeComponent();
        Mediator.PersonChangedDel += DetailView_PersonChanged;
    }

    void DetailView_PersonChanged(Person p)
    {
        BindData(p);
    }

    public void BindData(Person p)
    {
        lblPersonHairColor.Text = p.HairColor;
        lblPersonId.Text = p.IdPerson.ToString();
        lblPersonName.Text = p.Name;
        lblPersonNickName.Text = p.NickName;

    }
}

最后,我们在Form1.cs中有以下代码。这里我们调用OnPersonChanged,它调用任何订阅代理的代码。

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    Mediator.OnPersonChanged((Person)comboBox1.SelectedItem); //Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}

确定。这就是你如何在不使用事件和只使用代理的情况下实现的工作。我们只是将一个公共委托放入一个类 - 你可以使它成为静态或单个,或者其他什么。大。

但是,但是,我们不想做我刚才描述的事情。因为public fields are bad很多很多原因。那么我们有什么选择呢?正如John Skeet所描述的,以下是我们的选择:

  1. 一个公共委托变量(这就是我们上面刚刚做过的事情。不要这样做。我刚才告诉你为什么它不好)
  2. 将委托放入带有get / set的属性中(这里的问题是订阅者可以互相覆盖 - 所以我们可以向委托订阅一堆方法然后我们可能会意外地说PersonChangedDel = null,擦除这里剩下的另一个问题是,由于用户可以访问委托,他们可以调用调用列表中的目标 - 我们不希望外部用户有权访问何时提出我们的事件
  3. 使用AddXXXHandler和RemoveXXXHandler方法的委托变量
  4. 这第三个选项基本上是一个事件给我们的。当我们声明一个EventHandler时,它允许我们访问一个委托 - 不是公开的,不是作为属性,但是我们称之为只添加/删除访问者的事件。

    让我们看看同一个程序是什么样的,但现在使用的是Event而不是public委托(我还将我们的Mediator更改为单例):

    示例2:使用EventHandler而不是公共委托

    中保:

    class Mediator
    {
    
        private static readonly Mediator _Instance = new Mediator();
    
        private Mediator() { }
    
        public static Mediator GetInstance()
        {
            return _Instance;
        }
    
        public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate.
    
        public void OnPersonChanged(object sender, Person p)
        {
            var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>;
            if (personChangedDelegate != null)
            {
                personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
            }
        }
    }
    

    请注意,如果您在EventHandler上使用F12,它将向您显示定义只是一个带有额外“sender”对象的通用ified委托:

    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
    

    用户控制:

    public partial class DetailView : UserControl
    {
        public DetailView()
        {
            InitializeComponent();
            Mediator.GetInstance().PersonChanged += DetailView_PersonChanged;
        }
    
        void DetailView_PersonChanged(object sender, PersonChangedEventArgs e)
        {
            BindData(e.Person);
        }
    
        public void BindData(Person p)
        {
            lblPersonHairColor.Text = p.HairColor;
            lblPersonId.Text = p.IdPerson.ToString();
            lblPersonName.Text = p.Name;
            lblPersonNickName.Text = p.NickName;
    
        }
    }
    

    最后,这是Form1.cs代码:

    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
            Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
    }
    

    因为EventHandler想要和EventArgs作为参数,所以我创建了这个类,其中只包含一个属性:

    class PersonChangedEventArgs
    {
        public Person Person { get; set; }
    }
    

    希望这能够向您展示我们为什么会举办活动以及它们如何与众不同 - 但在功能上与代表们相同。

答案 5 :(得分:6)

您也可以在接口声明中使用事件,而不是代理人使用事件。

答案 6 :(得分:6)

事件与代表之间的误解真是太大了!委托指定TYPE(例如classinterface确实),而事件只是一种成员(例如字段,属性等)。并且,就像任何其他类型的成员一样,事件也有类型。但是,在事件的情况下,事件的类型必须由委托指定。例如,您不能声明由接口定义的类型的事件。

结束,我们可以进行以下观察:事件的类型必须由委托定义。这是事件和委托之间的主要关系,并在ECMA-335 (CLI) Partitions I to VI II.18定义事件一节中进行了描述:

  

在典型用法中,TypeSpec(如果存在)标识一个委托,其签名与传递给事件的fire方法的参数匹配。

但是,这一事实并不意味着事件使用了支持委托字段。实际上,事件可能使用您选择的任何不同数据结构类型的支持字段。如果您在C#中明确实现了一个事件,则可以自由选择存储事件处理程序的方式(请注意事件处理程序类型的实例事件,反过来强制性地是委托类型 ---来自之前的观察)。但是,您可以将这些事件处理程序(它们是委托实例)存储在数据结构中,例如ListDictionary或其他任何其他数据结构,甚至可以存储在支持委托字段中。但请不要忘记,您不必使用委托字段。

答案 7 :(得分:4)

.net中的事件是Add方法和Remove方法的指定组合,两者都期望某些特定类型的委托。 C#和vb.net都可以为添加和删除方法自动生成代码,这些方法将定义用于保存事件订阅的委托,并在该订阅委托中添加/删除传入的委托。当且仅当它非空时,VB.net还将自动生成代码(使用RaiseEvent语句)来调用订阅列表;出于某种原因,C#不会产生后者。

请注意,虽然使用多播委托来管理事件订阅是很常见的,但这并不是这样做的唯一方法。从公共角度来看,潜在事件订阅者需要知道如何让对象知道它想要接收事件,但是不需要知道发布者将使用什么机制来引发事件。还要注意,虽然在.net中定义事件数据结构的人显然认为应该有一种提高它们的公共方法,但C#和vb.net都不会使用该功能。

答案 8 :(得分:3)

以简单的方式定义事件:

对于具有两个限制

的委托,事件为 REFERENCE
  1. 无法直接调用
  2. 无法直接指定值(例如eventObj = delegateMethod)
  3. 以上两个是代表们的弱点,并在事件中得到解决。完整的代码示例显示fiddler的区别在于https://dotnetfiddle.net/5iR3fB

    切换Event和Delegate之间的注释以及调用/赋值给委托的客户端代码以了解差异

    这是内联代码。

     /*
    This is working program in Visual Studio.  It is not running in fiddler because of infinite loop in code.
    This code demonstrates the difference between event and delegate
            Event is an delegate reference with two restrictions for increased protection
    
                1. Cannot be invoked directly
                2. Cannot assign value to delegate reference directly
    
    Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
    */
    
    public class RoomTemperatureController
    {
        private int _roomTemperature = 25;//Default/Starting room Temperature
        private bool _isAirConditionTurnedOn = false;//Default AC is Off
        private bool _isHeatTurnedOn = false;//Default Heat is Off
        private bool _tempSimulator = false;
        public  delegate void OnRoomTemperatureChange(int roomTemperature); //OnRoomTemperatureChange is a type of Delegate (Check next line for proof)
        // public  OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 
        public  event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), 
    
        public RoomTemperatureController()
        {
            WhenRoomTemperatureChange += InternalRoomTemperatuerHandler;
        }
        private void InternalRoomTemperatuerHandler(int roomTemp)
        {
            System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");
        }
    
        //User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)
        public bool TurnRoomTeperatureSimulator
        {
            set
            {
                _tempSimulator = value;
                if (value)
                {
                    SimulateRoomTemperature(); //Turn on Simulator              
                }
            }
            get { return _tempSimulator; }
        }
        public void TurnAirCondition(bool val)
        {
            _isAirConditionTurnedOn = val;
            _isHeatTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
            System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
            System.Console.WriteLine("Heat :" + _isHeatTurnedOn);
    
        }
        public void TurnHeat(bool val)
        {
            _isHeatTurnedOn = val;
            _isAirConditionTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
            System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
            System.Console.WriteLine("Heat :" + _isHeatTurnedOn);
    
        }
    
        public async void SimulateRoomTemperature()
        {
            while (_tempSimulator)
            {
                if (_isAirConditionTurnedOn)
                    _roomTemperature--;//Decrease Room Temperature if AC is turned On
                if (_isHeatTurnedOn)
                    _roomTemperature++;//Decrease Room Temperature if AC is turned On
                System.Console.WriteLine("Temperature :" + _roomTemperature);
                if (WhenRoomTemperatureChange != null)
                    WhenRoomTemperatureChange(_roomTemperature);
                System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status
            }
        }
    
    }
    
    public class MySweetHome
    {
        RoomTemperatureController roomController = null;
        public MySweetHome()
        {
            roomController = new RoomTemperatureController();
            roomController.WhenRoomTemperatureChange += TurnHeatOrACBasedOnTemp;
            //roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.
            //roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
            roomController.SimulateRoomTemperature();
            System.Threading.Thread.Sleep(5000);
            roomController.TurnAirCondition (true);
            roomController.TurnRoomTeperatureSimulator = true;
    
        }
        public void TurnHeatOrACBasedOnTemp(int temp)
        {
            if (temp >= 30)
                roomController.TurnAirCondition(true);
            if (temp <= 15)
                roomController.TurnHeat(true);
    
        }
        public static void Main(string []args)
        {
            MySweetHome home = new MySweetHome();
        }
    
    
    }
    

答案 9 :(得分:0)

CovarianceContravariance为委托对象提供了额外的灵活性。另一方面,事件没有这样的概念。

  • Covariance允许您将方法分配给委托 方法的返回类型是从该类派生的类 指定委托的返回类型。
  • Contravariance允许您将方法分配给委托,其中 该方法的参数类型是该类的基类,即 指定为委托的参数。

答案 10 :(得分:0)

Delegate是类型安全的函数指针。事件是使用委托来实现发布者-订阅者设计模式的。

答案 11 :(得分:-1)

如果您检查中间语言,您将知道.net编译器将IL转换为IL中具有一些内置函数的密封类,例如一些从另一个类(可能称为“ SystemMulticast”)继承的内置函数,例如invoke,beginInvoke,endInvoke和委托类。 我猜Event是Delegate的子类,具有一些其他属性。

事件实例和委托之间的区别是,您不能在声明之外运行事件。 如果在A类中声明一个事件,则只能在A类中运行此事件。 如果在A类中声明了一个委托,则可以在任何地方使用该委托。 我认为这是它们之间的主要区别

答案 12 :(得分:-1)

对于2020年生活的人,想要一个明确的答案...

定义:

  • delegate:定义一个函数指针。
  • event:定义
    • (1)受保护的接口,
    • (2)个操作( += -= ),
    • (3)的优势:您不再需要使用new关键字。

关于受保护的形容词

// eventTest.SomeoneSay = null;              // Compile Error.
// eventTest.SomeoneSay = new Say(SayHello); // Compile Error.

还要注意Microsoft的这一部分:https://docs.microsoft.com/en-us/dotnet/standard/events/#raising-multiple-events

代码示例:

delegate

public class DelegateTest
{
    public delegate void Say(); // Define a pointer type "void <- ()" named "Say".
    private Say say;

    public DelegateTest() {
        say  = new Say(SayHello);     // Setup the field, Say say, first.
        say += new Say(SayGoodBye);
        
        say.Invoke();
    }
    
    public void SayHello() { /* display "Hello World!" to your GUI. */ }
    public void SayGoodBye() { /* display "Good bye!" to your GUI. */ }
}

event

public class EventTest
{
    public delegate void Say();
    public event Say SomeoneSay;  // Use the type "Say" to define event, an 
                                  // auto-setup-everything-good field for you.
    public EventTest() {
         SomeoneSay += SayHello;
         SomeoneSay += SayGoodBye;

         SomeoneSay();
    }
    
    public void SayHello() { /* display "Hello World!" to your GUI. */ }
    public void SayGoodBye() { /* display "Good bye!" to your GUI. */ }
}

参考:

事件与委托-解释C#中的事件和委托模式之间的重要区别以及它们为何有用。https://dzone.com/articles/event-vs-delegate