事件的 C# 实现

标签 c# class events

我正在学习类上的 C# 事件实现。

我有示例案例:

有一个 Sport 和 City 类继承自 Car 类。 Car 类具有由 Sport 和 City 类继承的基方法调用 OnBuy。在 OnBuy 方法内部,事件处理程序已分配给 Event Buy。

还有一个称为 LicenseService 的服务或类,它会在每次购买时生成许可证号。

我在这种情况下实现了事件驱动编程。 这是我的 git 示例:

https://github.com/adityosnrost/CSharpLearningEvent

问题:

  1. 这是在 C# 上使用 Event 的正确方法吗?

  2. 如果这是正确的。我可以将 OnBuy 方法覆盖到每个 child 吗?以及如果覆盖可用我该怎么办?

  3. 我可以做些什么来改进这个样本?

谢谢

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

最佳答案

如果我正在制作这个程序,我会做以下更改,

  • 事件购买签名

首先,它有两个参数 ObjectEventArgs,您只需要在处理程序方法中使用 Car(还有 Random 下面讨论了为什么)。

  • 我会在 Child 的构造函数中传递 LicenseService,并仅在构造函数中注册(订阅)事件。那将是更清洁的方式。
  • 会在父类中创建一个字符串成员CarName,这样每个 child 都可以在任何他们想要的地方使用它。
  • 还有一件事我没有在此代码中完成,我永远不会将事件命名为 Buy,而是将其命名为 Bought
  • (这仅适用于此场景)在您的代码中,您每次都在 GenerateLicense() 中创建新的 Random 对象。因此,如果您对该方法的两次调用都在短时间内,它将为两次调用生成相同的随机数。为什么?看这个Question - 或者您可以自己尝试下面的示例代码。所以我会在 GenerateLicense() 中传递已经创建的 Random 对象。所以 Random 对那个方法的每次调用都是通用的。

解释随机数行为的示例代码

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

更新

  • 正如 Mrinal Kamboj 所建议的(在下面的评论中),我们不应将 Events 暴露给外部代码。在此答案中也添加他的评论

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. 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

代码如下,

你的类(class)结构:

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

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

并调用流程

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

关于事件的 C# 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50384360/

相关文章:

c# - 如何获取给定日期的前三个日期.net

C# 代码约定 userMessage 参数

c++ - 具有静态方法的类或具有普通方法的类

javascript - 在 JavaScript ES6 类中无限期调用 "set function()"

将新添加的应用程序写入内存的 Windows 事件

c# - 部署时 Log4Net 不记录

c# - 当尝试写入文件抛出 UnauthorizedAccessException 时,需求不会抛出异常

python - 自修改Python类

android - 用于将 keyUp 事件的键码传递给应用程序的 Cordova 插件

jquery - jQuery 可以确定哪些 div 当前位于用户的浏览器 View 中吗?