简体   繁体   English

C#事件的实现

[英]C# Implementation of Event

I am learning about C# event implementation on classes. 我正在学习关于类的C#事件实现。

I have example case: 我有例子:

There is a Sport and City classes inherit from Car class. Car class继承了Sport和City课程。 Car class has base method call OnBuy that inherited by Sport and City classes. Car class具有由Sport和City类继承的基本方法调用OnBuy。 Inside OnBuy method, a event handler has beed assigned to Event Buy. 在OnBuy方法中,事件处理程序已分配给Event Buy。

There is also a Service or Class called LicenseService, that generate license number every time has been bought. 还有一个名为LicenseService的服务或类,每次购买时都会生成许可证号。

I implemented event driven programming in this case. 在这种情况下,我实现了事件驱动编程。 Here is my git sample: 这是我的git示例:

https://github.com/adityosnrost/CSharpLearningEvent https://github.com/adityosnrost/CSharpLearningEvent

Questions: 问题:

  1. Is this the right way to use Event on C# ? 这是在C#上使用Event的正确方法吗?

  2. if this is a right one. 如果这是正确的。 Can I override OnBuy method into each childs ? 我可以将OnBuy方法覆盖到每个孩子身上吗? and What can I do if override is available ? 如果覆盖可用,我该怎么办?

  3. What can I do to make it better from this sample ? 我可以做些什么来使这个样本更好?

Thank you 谢谢

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car();
        Sport sport = new Sport();
        City city = new City();

        //car.Name();
        //sport.Name();
        //city.Name();

        //Console.ReadLine();

        LicenseService ls = new LicenseService();

        city.Buy += ls.GenerateLicense;

        city.OnBuy();

        Console.ReadLine();
    }
}

internal class Car
{
    internal virtual void Name()
    {
        Console.WriteLine("Car");
    }

    internal event EventHandler Buy;

    internal virtual void OnBuy()
    {
        EventHandler handler = Buy;
        if (null != handler)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

internal class Sport: Car
{
    internal override void Name()
    {
        Console.WriteLine("Sport");
    }
}

internal class City: Car
{
    internal override void Name()
    {
        Console.WriteLine("City");
    }
}

internal class LicenseService
{
    internal void GenerateLicense(object sender, EventArgs args)
    {
        Random rnd = new Random();

        string carType = sender.GetType().ToString();

        string licenseNumber = "";

        for(int i = 0; i < 5; i++)
        {
            licenseNumber += rnd.Next(0, 9).ToString();
        }

        Console.WriteLine("{1} Car has been bought, this is the license number: {0}", licenseNumber, carType);
    } 
}

If I was making this program, I would make following changes, 如果我正在制作这个节目,我会做出以下更改,

  • Signature of Event Buy 活动签名

first it was having two parameters Object and EventArgs where you only need Car in handler method (and also Random discussed below why). 首先它有两个参数ObjectEventArgs ,你只需要Car in handler方法(以及随后的Random讨论原因)。

  • I would pass LicenseService in constructor of Child, and register (subscribe) Event in constructor only. 我会在Child的构造函数中传递LicenseService ,并LicenseService在构造函数中注册(subscribe)Event。 that would be more cleaner way. 那将是更清洁的方式。
  • Would make a string member CarName in parent class, so every child can use it anywhere they want. 会在父类中创建一个字符串成员CarName ,这样每个孩子都可以在任何他们想要的地方使用它。
  • One more thing which i haven't done in this code, I would never name an event Buy , instead I would name it Bought . 还有一件事,我没有在这段代码中做过,我永远不会命名一个事件Buy ,而是我会命名为Bought
  • (This is specific to this scenario only) In your code, inside GenerateLicense() you are creating new object of Random every time. (这仅适用于此场景)在您的代码中, GenerateLicense()内部每次都会创建Random新对象。 Thus If your two calls for that method are within no time , It will generate same Random number for both calls. 因此,如果您对该方法的两次调用都在很短的时间内,它将为两个调用生成相同的随机数。 Why? 为什么? see this Question - or you can try below sample code by yourself. 看到这个问题 - 或者您可以自己尝试下面的示例代码。 So I would pass Already created object of Random in GenerateLicense() . 所以我会在GenerateLicense()传递已经创建的Random对象。 So Random will be common for every call of that method. 因此,对于该方法的每次调用, Random都是常见的。

Sample code to explain Random number's behavior 用于解释随机数行为的示例代码

        //as object of Random numbers are different,
        //they will generate same numbers
        Random r1 = new Random();
        for(int i = 0; i < 5; i++)
            Console.WriteLine(r1.Next(0, 9));
        Random r2 = new Random();
        for(int i = 0; i < 5; i++)
            Console.WriteLine(r2.Next(0, 9));

Update 更新

  • As suggested by Mrinal Kamboj (in comments below), we should not make Events exposed to external code. 正如Mrinal Kamboj所建议的那样(在下面的评论中),我们不应该将Events暴露给外部代码。 Adding his comment too in this answer 在这个答案中添加他的评论

Two points, EventHandler Buy cannot be allowed to be directly accessed outside it shall be private, since anyone otherwise can set that to null and all subscription is gone. 两点, EventHandler Buy不允许直接访问外部它应该是私有的,因为任何人都可以将其设置为null并且所有订阅都消失了。 It needs an Event Accessor, so that event can be accessed using += and -= operators and there itself you make it thread safe for multiple subscribers, else there will be race condition, check a simple example 它需要一个事件访问器,因此可以使用+=-=运算符访问事件,并且它本身可以使多个订户的线程安全,否则会出现竞争条件,请查看一个简单的示例

following is code, 以下是代码,

your class structure: 你的班级结构:

internal delegate void EventHandler(Car car, Random rnd);
internal class Car
{
    internal string CarName;
    internal virtual void SetName()
    {
        this.CarName = "car";
    }

    //Edit : As Mrinal Kamboj suggested in comments below
    //here keeping event Buy as private will prevent it to be used from external code
    private event EventHandler Buy;
    //while having EventAccessros internal (or public) will expose the way to subscribe/unsubscribe it
    internal event EventHandler BuyAccessor
    {
        add 
        {
            lock (this)
            {
                Buy += value;
            }
        }
        remove
        {
            lock (this)
            {
                Buy -= value;
            }
        }
    }

    internal virtual void OnBuy(Random rnd)
    {
        if (Buy != null)
            Buy(this, rnd);
    }
}

internal class Sport: Car
{
    LicenseService m_ls;
    internal Sport(LicenseService ls)
    {
        this.m_ls = ls;
        this.BuyAccessor += ls.GenerateLicense;
        SetName();
    }

    internal override void SetName()
    {
        this.CarName = "Sport";
    }
}

internal class City: Car
{
    LicenseService m_ls;
    internal City(LicenseService ls)
    {
        this.m_ls = ls;
        this.BuyAccessor += ls.GenerateLicense;
        SetName();
    }
    internal override void SetName()
    {
        this.CarName = "City";
    }
}

LicenseService class LicenseService

internal class LicenseService
{
    internal void GenerateLicense(Car sender, Random rnd)
    {
        string carName = sender.CarName;
        string licenseNumber = "";
        for(int i = 0; i < 5; i++)
        {
            licenseNumber += rnd.Next(0, 9).ToString();
        }
        Console.WriteLine("{1} Car has been bought, this is the license number: {0}", licenseNumber, carName);
    } 
}

and calling the flow 并呼吁流动

static void Main(string[] args)
{
    Random rnd = new Random();
    LicenseService ls = new LicenseService();
    Sport sport = new Sport(ls);
    City city = new City(ls);

    city.OnBuy(rnd);
    sport.OnBuy(rnd);

    Console.ReadLine();
}

It's a bit of a long answer, but I will address your example first and then move on to some general tips. 这是一个很长的答案,但我会首先解释你的例子,然后继续一些一般性的提示。 Bare in mind all code is pseudo code, it will not compile without syntax adjustments. 请记住,所有代码都是伪代码,如果没有语法调整,它将无法编译。

First of all, your logical structure does not make sense, which is why it might be hard for you to pinpoint if this is correct or not. 首先,你的逻辑结构没有意义,这就是为什么你可能很难确定这是否正确。

For instance in real world, you do not address a car to buy it, you address a shop or a service that sells them. 例如,在现实世界中,您不会在汽车上购买它,而是在商店或销售它们的服务中找到它们。 You only address a car to drive, or use other functionality that it offers . 您只能驾驶汽车或使用其提供的其他功能。 The car does not assign itself a license. 汽车没有为自己分配许可证。 Finally, a purchase is a process that is generally linear (and can be expressed by a method, without triggers) if you take a basic sellers/buyer example. 最后,如果您采用基本的卖家/买家示例,则购买是一个通常是线性的(并且可以通过方法表示,没有触发器)。 So when you call shop.BuyCar( sportsCar ) all of the purchase logic can be called from the buy method. 因此,当您调用shop.BuyCar( sportsCar ) ,可以从buy方法调用所有购买逻辑。

Class Shop{ 

    public Car BuyCar( carWithoutLicense ){
        //Purchase logic
        LicenseService.AssignLicense( carWithoutLicense ).
        return carWithoutLicense.
    }
}
//A person would call this method, no the car

A better example of correctly utilized event would be one of the alert lights on the front panel of a car, because it is there to notify the driver of something he/she might want to react to. 正确使用事件的一个更好的例子是汽车前面板上的警示灯之一,因为它可以通知驾驶员他/她可能想要做出的反应。 For instance: a check engine light. 例如:检查发动机灯。

class Car {
   Bool CheckEngingLightIsOn = false;
   public void Ride( ... ){
    if( enginge.faultDetected == true ){
       TurnOnCheckEngineAlert( );
    }
   }
   public void TurnOnCheckEngineAlert( ){
      CheckEngingLightIsOn = true;
      if( CheckEngineLightSwitch != null ){
         CheckEngineLightSwitch.Invoke( ... )
      }
   }
}

class Driver {
   public Driver( Car car ){
      this.car = car;
      if( driverState != Drunk ){
       car.CheckEngineLightSwitch = TakeAction;
      }
   }
   public Drive( ){
      car.Ride( );
   }
   public void TakeAction( Car car, EventArgs e ){
      //get out, open the hood, check the engine...
      if( car.CheckEngingLightIsOn == true ){ "Light turned on
        //Check Engine
      }else{
        //continue driving
      }
   }
}

Without getting too deep into abstraction, notice the chain of events: 如果不深入抽象,请注意事件链:

  • Driver drives a car and does not worry about other things (such as that check engine light) until they happen. Driver驾驶汽车并且在发生之前不担心其他事情(例如检查发动机灯)。
  • If Car detects a fault, it turns on the check engine light and there are event handlers on that event (subscribers) the car triggers the event. 如果Car检测到故障,它会打开检查引擎灯,并且该事件(订户)上有事件处理程序,汽车会触发事件。
  • The event fires, but the driver has to be subscribed to it to notice the change. 事件触发,但驱动程序必须订阅它才能注意到更改。
  • ONLY if the driver is subscribed to that event (in this case, if not drunk) he will take action, based on that event. 只有当驾驶员订阅了该事件时(在这种情况下,如果没有喝醉),他将根据该事件采取行动。

This example is fundamentally different to your example, because: 此示例与您的示例根本不同,因为:

  • While driving the car, the driver doesn't need to pay attention to check engine light all the time (even though, he can check it). 驾驶汽车时,驾驶员无需始终注意检查发动机灯(即使他可以检查)。
  • It IS the standard process of a car to check engine state and represent it on the engine light. 这是检查发动机状态并在发动机灯上表示它的汽车的标准过程。
  • Driver and the Car both affect each others further actions and this logic is inefficient if express linearly. 驱动程序和汽车都会影响彼此的进一步行动,如果线性表达,这种逻辑效率低下。

In other words, Buying process is set in stone (paying, license, goods transfer) and skip essential steps cannot be skipped. 换句话说,购买过程是固定的(支付,许可,货物转移)和跳过必不可少的步骤不能跳过。 The process of a car moving itself is not set in stone, because neither the car nor the driver knows what will happen in journey. 汽车自行移动的过程并非一成不变,因为汽车和司机都不知道旅程中会发生什么。 IE Driver might drive to destination without stopping (if no fault develops or if he doesn't notice the light) or the car might force the driver to stop when he notices the check engine light go on (essentially, they both control each other in a sense). IE驱动程序可能会在没有停止的情况下开车到达目的地(如果没有出现故障或者他没有注意到灯光),或者当车辆注意到检查发动机指示灯亮起时汽车可能会强迫驾驶员停车(基本上,他们都在相互控制感觉)。


Some general tips about your use-case 关于您的用例的一些常规提示

In your example You have made a somewhat complex use-case (with incorrectly placed logic), which it will run, but will be structurally incorrect (and bad logical structure tends to lead to human mistakes when designing further logic). 在你的例子中你做了一个有点复杂的用例(错误放置逻辑),它将运行,但结构上是不正确的(在设计更多逻辑时,错误的逻辑结构往往会导致人为错误)。

First thing you should look at is what logic is relevant to each object. 您应该首先看到的是与每个对象相关的逻辑。 Does an event/method in your object represent something that your object does (ie functionality that an object performs itself) or something that affects your object but the object itself does not do anything in process? 对象中的事件/方法是代表对象所做的事情(即对象自身执行的功能)还是影响对象的东西,但对象本身在进程中没有做任何事情? For instance: a car does "riding" on it's own (even if start of this process and all of its parameters, such as speed or direction are controlled by the driver); 例如:汽车自行“骑”(即使这个过程开始,其所有参数,如速度或方向都由驾驶员控制); assigning license to a car happens completely outside of the car structure and the car only get an attribute changed (license) in process. 为汽车分配许可证完全发生在汽车结构之外,并且汽车只会在过程中获得属性更改 (许可证)。

This distinction is important because only logic performed by your object is relevant to that object, and by extension, any logic that is performed by another object and only affects your object is irrelevant belongs somewhere else. 这种区别很重要,因为只有对象执行的逻辑才与该对象相关,并且通过扩展,任何由另一个对象执行并且仅影响您的对象的逻辑都属于其他地方。 So Buy definitely does not belong in the car and Ride (the process of moving) belongs to the car. 因此, Buy绝对不属于汽车而且Ride(移动过程)属于汽车。

Secondly, your naming will go a long way to help you understand this topic. 其次,您的命名将有助于您理解此主题。 Methods represent actions and should be named as such ( Shop.BuyCar , Car.Ride , Driver.Drive ), events represent reaction triggers ( Car.CheckEngineLightSwitch ) and event handlers represent reactions to an action (a reaction is still an action, so no special naming is needed, but you can name to to make a distinction between an action and a reaction). 方法代表的行动和应该被命名为这样( Shop.BuyCarCar.RideDriver.Drive ),事件代表反应触发器Car.CheckEngineLightSwitch )和事件处理程序表示反应的动作(反应仍然是一个动作,所以没有需要特殊命名,但您可以命名为区分动作和反应)。

Nope. 不。

Events should not be called by anyone other than the class that owns the event (or, if you know what you're doing, classes that inherit from the class that owns the event - ad even then, they should probably pay attention to the original implementation to avoid subtle issues) 除了拥有该事件的类之外,任何人都不应该调用事件(或者,如果您知道自己在做什么,那么继承自拥有该事件的类的类 - 广告即使这样,他们也应该注意原始事件实施以避免细微问题)

Basically, a subscription to an event is a promise that, when a given thing happens, you'll call the functions you've fed to it from subscribers. 基本上,对事件的订阅是一种承诺,当给定的事情发生时,您将调用您从订阅者那里获得的功能。 The event itself is simple the code construct that allows you to make that subscription, without having to know or implement the intricacies of multicast function pointer invocation. 事件本身很简单,代码构造允许您进行订阅,而无需知道或实现多播函数指针调用的复杂性。

Otherwise, you're just calling a function and may as well not bother with an event. 否则,你只是在调用一个函数,也可能不会对事件感到烦恼。

Events are essentially intentional code injection - they allow you to make some other class execute arbitrary code that you wrote, when they do a thing. 事件本质上是有意的代码注入 - 它们允许你让一些其他类执行你编写的任意代码,当他们做某事时。

I recommend you to inject licence service to car, and call generate licence function when buy is called, 我建议你向汽车注入许可证服务,并在购买时调用生成许可证功能,

using System;

namespace ConsoleApp1.Test
{

    class Program
    {
        static void Maintest(string[] args)
        {
            ILicenseService licenseService = new LicenseService();

            Sport sport = new Sport(licenseService);
            City city = new City(licenseService);

            //car.Name();
            //sport.Name();
            //city.Name();

            //Console.ReadLine();            

            city.OnBuy();

            Console.ReadLine();
        }
    }

    internal abstract class Car
    {
        protected readonly ILicenseService licenseService;

        public Car(ILicenseService _licenseService)
        {
            licenseService = _licenseService;
        }

        internal virtual void Name()
        {
            Console.WriteLine("Car");
        }

        internal event EventHandler Buy;

        internal virtual void OnBuy()
        {
            // TODO
        }
    }

    internal class Sport : Car
    {
        public Sport(ILicenseService _licenseService) : base(_licenseService)
        {

        }

        internal override void Name()
        {
            Console.WriteLine("Sport");
        }

        internal override void OnBuy()
        {
            licenseService.GenerateLicense(new object());
        }
    }

    internal class City : Car
    {
        public City(ILicenseService _licenseService) : base(_licenseService)
        {

        }

        internal override void Name()
        {
            Console.WriteLine("City");
        }

        internal override void OnBuy()
        {
            licenseService.GenerateLicense(new object());
        }
    }

    internal interface ILicenseService
    {
        void GenerateLicense(object param);
    }

    internal class LicenseService : ILicenseService
    {
        public void GenerateLicense(object param)
        {
            // do your stuff
        }
    }

}

1) Is this the right way to use Event on C# ? 1)这是在C#上使用Event的正确方法吗?

Not completely. 不完全的。 The OnBuy should be a protected virtual method. OnBuy应该是受保护的虚拟方法。 That also excludes you calling it from the Main() method. 这也排除了你从Main()方法调用它。

More common would be to call someCar.Buy() and then Buy() wil trigger OnBuy(). 更常见的是调用someCar.Buy()然后Buy()将触发OnBuy()。

2) if this is a right one. 2)如果这是正确的。 Can I override OnBuy method into each childs ? 我可以将OnBuy方法覆盖到每个孩子身上吗? and What can I do if override is available ? 如果覆盖可用,我该怎么办?

Yes, you can override it See that as a more effective way of subscribing to yourself (which would be the alternative). 是的,你可以覆盖它看到这是一种更有效的订阅自己的方式(这将是另一种选择)。

You can do anything needed when a specific type of Car is bought. 购买特定类型的汽车时,您可以执行任何操作。 But do alwas call base.OnBuy() 但是做alwas call base.OnBuy()

3) What can I do to make it better from this sample ? 3)从这个样本中我可以做些什么来改善它?

The CreateLicense just doesn't sound like a good candidate for an event, it would be more of a business rule called by a CarDealer. CreateLicense听起来不像是一个事件的好候选者,它更像是CarDealer调用的业务规则。

Following modern design rules, a Car would be a rather passive entity object (Anemic Domain Model). 遵循现代设计规则,汽车将是一个相当被动的实体对象(贫血领域模型)。

Events would usually be used to tell other components "I have changed, do your thing", not to perform essential actions on self. 事件通常用于告诉其他组件“我已经改变,做你的事”,而不是对自己执行必要的操作。

Following would be my preferred design: 以下是我的首选设计:

class Program
{
    static void Main(string[] args)
    {
        Car car = new Sport();
        car.BuyEvent += License.GenerateLicense;        
        car.OnBuy();
        car = new City();
        car.BuyEvent += License.GenerateLicense;
        car.OnBuy();
    }
}

internal abstract class Car
{
    internal abstract void Name();

    protected abstract event EventHandler Buy;

    public abstract event EventHandler BuyEvent;

    public abstract void OnBuy();   
}

internal class Sport : Car
{
    internal override void Name()
    {
        Console.WriteLine("Sport Car");
    }

    protected override event EventHandler Buy;

    public override event EventHandler BuyEvent
    {
        add
        {
            lock (this)
            {
                Buy += value;
            }
        }

        remove
        {
            lock (this)
            {
                Buy -= value;
            }
        }
    }

    public override void OnBuy()
    {
        if (Buy != null)
        {
            Buy(this, EventArgs.Empty);
        }
    }

}

internal class City : Car
{
    internal override void Name()
    {
        Console.WriteLine("City Car");
    }

    protected override event EventHandler Buy;

    public override event EventHandler BuyEvent
    {
        add
        {
            lock (this)
            {
                Buy += value;
            }
        }

        remove
        {
            lock (this)
            {
                Buy -= value;
            }
        }
    }

    public override void OnBuy()
    {
        if (Buy != null)
        {
            Buy(this, EventArgs.Empty);
        }
    }
}

internal static class License
{

    public static void GenerateLicense(object sender, EventArgs args)
    {
        Random rnd = new Random();

        string carType = sender.GetType().Name;

        string licenseNumber = "";

        for (int i = 0; i < 5; i++)
        {
            licenseNumber += rnd.Next(0, 9).ToString();
        }

        Console.WriteLine($"{carType} Car has been bought, this is the license number: {licenseNumber}");
    }

}

Important points: 重点:

  1. Make the Car base class abstract, it shall only be derived and used like City / Sport Car 使Car基类抽象,它只能像City / Sport Car一样派生和使用
  2. Add event add / remove accessor in each of the child class for custom processing of the event. 在每个子类中添加event add / remove accessor以自定义事件处理。 Make the accessor thread safe for shared client access 使访问者线程对共享客户端访问安全
  3. Make License - GenerateLicense as either static, as there's no state / data in the class or integrate them too with the City / Sport class implementation by having another base abstract method Make License - GenerateLicense是静态的,因为类中没有状态/数据,或者通过另一个基本的抽象方法将它们与City / Sport class实现集成在一起
  4. Abstract class Car shall be injected with Sport \\ City objects at the runtime and shall be used for event subscription via base class Car object 抽象类Car应在运行时注入Sport \\ City对象,并应通过基类Car对象用于事件订阅

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM