C# 学习笔记(7)—— 事件

2023-10-20 18:48:58 浏览数 (1)

什么是事件

事件是基于委托之上的新特性,自然也是 C# 独有的概念。事件理解起来不难,意思就是他的字面意思,就是我们日常理解的事件

使用事件

事件与委托代码上的区别就是多了一个 event 关键字,使用方式有点变化

如何定义事件

声明上就是多了一个 event,如下所示

代码语言:javascript复制
// EventHandler 是在 System 命名空间下定义的委托类型
public event EventHandler birthday;

// 自定义
public delegate void CustomHandler;
public event CustomHandler custom;

订阅和取消事件

我这里基于事件来实现博客的发布、订阅和取消订阅操作。具体逻辑就是:读者张三、李四订阅了某博主,博主发布了一篇博客,张三、李四能正常接受博客发布通知。张三不想再接受这个博主的博客推送了,取消订阅,之后再也收不到这位博主的博客通知了

如果不适用事件特性,我们怎么实现?

代码语言:javascript复制
public class AuthorBlog
{
    public List<Reader> Readers = new List<Reader>();

    public void Publish(string name)
    {
        Console.WriteLine("作者发布了:"   name);
        foreach (var reader in Readers)
        {
            reader.Receive(name);
        }
    }
}

public class Reader
{
    public string name { get; set; }

    public void Subscribe(AuthorBlog blog)
    {
        blog.Readers.Add(this);
    }

    public void Unsubscribe(AuthorBlog blog)
    {
        blog.Readers.Remove(this);
    }

    public void Receive(string name)
    {
        Console.WriteLine($"{this.name}接受到了博客:{name}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var blog = new AuthorBlog();

        var user1 = new Reader();
        user1.name = "张三";
        user1.Subscribe(blog);

        var user2 = new Reader();
        user2.name = "王二";
        user2.Subscribe(blog);

        blog.Publish("《钢铁是怎样炼成的》");

        user1.Unsubscribe(blog);

        blog.Publish("《边城》");
        Console.ReadKey();
    }
}

很简单,我用一个集合将读者对象存储起来,我每次发布都去循环去调用读者的接受方法。或者做的再好一点就是只存储读者对象的 Receive 方法

代码语言:javascript复制
public delegate void ReceiveHandler(string name);
public ReceiveHandler Receive;

// Receive  = ...
// Receive -= ...

那 event 有什么作用呢?我们看下面这个代码示例

代码语言:javascript复制
class Program
{
    public delegate void PublishHandler(string name);
    public class AuthorBlog
    {
        public event PublishHandler Publish;

        public void OnPublishing(string name)
        {
            Console.WriteLine("作者发布了:"   name);
            if (Publish != null)
            {
                Publish(name);
            }
        }
    }

    public class Reader
    {
        public string? name { get; set; }
        public void Subscribe(AuthorBlog blog)
        {
            blog.Publish  = Receive;
        }
        public void Unsubscribe(AuthorBlog blog)
        {
            blog.Publish -= Receive;
        }

        public void Receive(string name)
        {
            Console.WriteLine($"{this.name}接受到了博客:{name}");
        }
    }


    static void Main(string[] args)
    {
        var blog = new AuthorBlog();

        var user1 = new Reader();
        user1.name = "张三";
        user1.Subscribe(blog);

        var user2 = new Reader();
        user2.name = "王二";
        user2.Subscribe(blog);

        blog.OnPublishing("《钢铁是怎样炼成的》");

        user1.Unsubscribe(blog);

        blog.OnPublishing("《边城》");
        Console.ReadKey();
    }

}

我们多加了一个OnPublishing方法,在这个方法里我们去执行了事件Publish,那么不加这个event有什么影响?

区别在于 调用方式

event无法通过blog.Publish(...)执行委托链,只能在类内部调用

不加event可以通过blog.Publish(...)执行委托链

EventHandler

前面我们用自定义委托类型来定义事件,我们还可以用 .Net 类库中预定义的委托类型EventHandler来定义事件,这也是实际开发中普遍采用的一种方式

定义

定义如下

代码语言:javascript复制
public delegate void EventHandler(Object sender, EventArgs e);

我们可以看出以下几点

  • 该委托返回类型为 void,因此实例化委托类型的方法也需要满足这点
  • 第一个参数 sender 负责保存对触发事件对象的引用,其类型为 object
  • 第二个参数 e 负责保存事件数据,EventArgs类也是 .Net 类库中定义的类,它不保存任何数据

扩展 EventArgs 类

因为EventHandler只用于处理不包含事件数据的事件。如果我们想要在由这种方式定义的事件中包含事件数据,则可以通过派生EventArgs类实现。实现代码如下

代码语言:javascript复制
class Program
{
    public delegate void PublishHander(object sender, BlogAuthorEventArgs e);
    public class AuthorBlog
    {
        public event PublishHander PublishEvent;

        public void OnPublishing(string name)
        {
            Console.WriteLine("作者发布了:"   name);
            if (PublishEvent != null)
            {
                PublishEvent(this, new BlogAuthorEventArgs());
            }
        }
    }

    public class Reader
    {
        public string? name { get; set; }
        public void Subscribe(AuthorBlog blog)
        {
            blog.PublishEvent  = new PublishHander(Receive);
        }
        public void Unsubscribe(AuthorBlog blog)
        {
            blog.PublishEvent -= new PublishHander(Receive);
        }

        public void Receive(object s, BlogAuthorEventArgs args)
        {
            Console.WriteLine($"{this.name}接受到了博客:{name}");
        }
    }

    public class BlogAuthorEventArgs : EventArgs
    {
        public string name { get; set; }
        public BlogAuthorEventArgs() { }
        public BlogAuthorEventArgs(string name)
        {
            this.name = name;
        }
    }


    static void Main(string[] args)
    {
        var blog = new AuthorBlog();

        var user1 = new Reader();
        user1.name = "张三";
        user1.Subscribe(blog);

        var user2 = new Reader();
        user2.name = "王二";
        user2.Subscribe(blog);

        blog.OnPublishing("《钢铁是怎样炼成的》");

        user1.Unsubscribe(blog);

        blog.OnPublishing("《边城》");
        Console.ReadKey();
    }

}

事件的本质

从事件的使用过程可以看出,事件的定义包含了委托类型。那么,事件和委托间到底有着怎样的关系呢?

代码语言:javascript复制
class Program
{
    public delegate void PublishHandler(string name);
    public event PublishHandler PublishEvent;
    
    static void Main(string[] args)
    {
        
    }
}

可以通过 IL 工具去查看中间语言

归纳总结

看到这里,基本上你已经掌握了 C# 1.0 最重要的两个特性了。看到这里大家肯定觉得 C# 函数传递非常麻烦,虽然能实现,但是要写很多代码,还能不能再方便一点?

0 人点赞