C# 委托与事件总结

C# 中的委托和事件可以说是超级拦路虎了,一个不小心就容易让人直接放弃学习。最近几天花了点时间钻研了一下,查看了一些资料,希望做个总结。

我还达不到技术大佬的级别,主要就是做个自我总结,很推荐大家看一下我下面发的参考资料里的文章和视频,那些是真正的大佬产出的内容。

推荐资料

张子扬博客中的文章:

刘铁猛的视频:

官方文档:C# 官方文档


把委托单独拿出来讲倒是不难理解,但是和和事件结合就会产生化学反应,第一次学习的时候直接爆炸了…

委托

委托其实就是个类(因此它定义时往往和类平级),它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递(其实就是一直说的函数式编程),主要目的就是为了让程序有更好的拓展型。

即使不学习这个概念,基本上不会影响我们编写程序(大不了就是 if else 走天下,来新需求就大规模的改代码…),但是这个概念对于写出高质量、优雅的程序至关重要。

类比一下其他语言,关于完成“传递方法”这件事,各个语言有不同的做法:

  • C / C++ 通过函数指针传递函数,而委托可以看作函数指针的 “升级版”,它比函数指针更加安全
  • Java 中没有委托的概念,需要依靠接口来实现传递方法,其中一些典型的函数式接口如:Supplier 优化不一定执行的代码、Consumer:接收一个值决定要做什么、Predicate:让过滤条件更灵活、Function:实现类型转换
  • JavaScript 中“函数是一等公民”,可以直接实现将函数作为参数与返回值

可以看到,函数式编程是个重要的概念,各个语言基于这个概念只是实现方式不同,而 C# 的实现方式就是 委托

使用 C# 内置委托:Action 和 Func

先学会怎么用,再探究怎么写,因此我们需要先学会使用 AcitonFunc

ActionFunc:是 C# 中内置的委托类型:

  • Action 用于委托没有返回值的函数
    Action 表示无参,无返回值的委托
    Action<int, string> 表示有传入参数 int、string 无返回值的委托
    Action<int, string, bool> 表示有传入参数 int、string、bool 无返回值的委托
  • Func 用于委托有返回值的函数
    Func<int> 表示无参,返回值是 int 的委托
    Func<int, int> 表示返回值是 int,传入参数 int 的委托
    Func<double, double, int> 表示返回值是 double,传入参数是 double、int 的委托
class Test
{
    class Calculator
    {
        public void Report() => Console.WriteLine("I have 3 methods");
        public int Add(int a, int b) => a + b;
    }

    static void Main()
    {
        Calculator calculator = new Calculator();

        // Action用于委托没有返回值的函数
        Action action = new Action(calculator.Report);
        action(); // action.Invoke() 也可以

        // Func的范型的第一个参数是返回值,然后是函数的输入参数
        Func<int, int, int> func = new Func<int, int, int>(calculator.Add);
        Console.WriteLine(func(1, 2)); // func.Invoke(1, 2); 
    }
}

自定义委托

在学会使用委托的情况下,再来自己声明一个委托试试看,然后使用它

委托是类,所以声明位置是和 class 处于同一个级别。但 C# 允许嵌套声明类(一个类里面可以声明另一个类),所以有时也会有 delegate 在 class 内部声明的情况。

// 使用 delegate 声明委托
public delegate double Calc(double x, double y);

class Calculator
{
    public double Mul(double x, double y) => x * y;
    public double Div(double x, double y) => x / y;
}

class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();

        // 创建委托实例的完整写法
        Calc calc1 = new Calc(calculator.Mul);
        // 创建委托实例的简单写法
        Calc calc2 = calculator.Div;

        Console.WriteLine(calc1(6, 2)); // 12
        Console.WriteLine(calc2(6, 2)); // 3
    }
}

委托的综合实例

委托的一般使用场景:

  • 模版方法,提高代码复用性
  • 回调函数,将某个方法传入主调方法中,根据其逻辑决定是否调用该方法

综合实例:

using System;

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

        // Func 用于有返回值的函数,范型中第一个类型即返回值类型
        Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
        Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);

        var wrapFactory = new WrapFactory();
        var logger = new Logger();
        // Action 用于没有返回值的函数,范型中的类型即传入参数类型
        Action<Product> log = new Action<Product>(logger.Log);

        Box box1 = wrapFactory.WrapProduct(func1, log);
        Box box2 = wrapFactory.WrapProduct(func2, log);

        Console.WriteLine(box1.Product.Name);
        Console.WriteLine(box2.Product.Name);
    }
}

// 日志类 - 一般不属于业务,属于额外操作
class Logger
{
    public void Log(Product product)
    {
        // Now 是带时区的时间,存储到数据库应该用不带时区的时间 UtcNow。
        Console.WriteLine("Product '{0}' created at {1}.Price is {2}", product.Name, DateTime.UtcNow, product.Price);
    }
}

// 产品类 - 业务
class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
}

// 包装类 - 业务
class Box
{
    public Product Product { get; set; }
}

// 包装工厂类 - 用于包装产品
class WrapFactory
{
    // 模板方法,提高复用性
    // 所有的产品包装过程都需要遵守这个规范,
    // 对WrapFactory来说不关心具体对象, 只负责执行这个操作
    public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallBack)
    {
        var box = new Box();
        Product product = getProduct.Invoke();

        // 只 log 价格高于 50 的
        if (product.Price >= 50)
        {
            logCallBack(product);
        }

        box.Product = product;
        return box;
    }
}

// 产品工厂类 - 用于生产不同的产品
class ProductFactory
{
    public Product MakePizza()
    {
        return new Product { Name = "Pizza", Price = 12 }; ;
    }

    public Product MakeToyCar()
    {
        return new Product { Name = "Toy Car", Price = 100 }; ;
    }
}

多播委托

多播委托就是,一个委托内部有多个方法,当该委托被调用时,其中的方法依次调用:

class Program
{
    static void Main(string[] args)
    {
        Student stu1 = new Student { Name = "aaa" };
        Student stu2 = new Student { Name = "bbb" };

        Action action1 = new Action(stu1.Study);
        Action action2 = new Action(stu2.Study);

        // 单播委托 -> 每个委托依次调用
        //action1.Invoke();
        //action2.Invoke();

        // 多播委托 -> 多个委托合并为一个委托再调用
        action1 += action2;
        action1.Invoke();
    }
}

class Student
{
    public string Name { get; set; }
    public void Study() => Console.WriteLine(Name + " is doing studying!");
}

事件

事件声明有完整声明和简略声明两种,简略声明是完整声明的语法糖

事件无论是从表层约束还是从底层实现都是依赖于委托的

事件声明的完整格式

声明委托类型 ≠ 声明委托类型字段

  • 委托类型是与类同级别的
  • 委托类型字段是类内部的一个字段
using System;
using System.Threading;

namespace EventExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1.事件拥有者
            var customer = new Customer();
            // 2.事件响应者
            var waiter = new Waiter();
            // 3.Order 事件成员 5. +=事件订阅
            customer.Order += waiter.Action;

            customer.Action();
            customer.PayTheBill();
        }
    }

    // 该类用于传递点的是什么菜,作为事件参数,需要以 EventArgs 结尾,且继承自 EventArgs
    public class OrderEventArgs : EventArgs
    {
        // 菜品名
        public string DishName { get; set; }
        // 菜品大小
        public string Size { get; set; }
    }

    // 声明一个委托类型,因为该委托用于事件处理,所以以 EventHandler 结尾
    // 注意委托类型的声明和类声明是平级的
    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

    public class Customer
    {
        // 委托类型字段
        private OrderEventHandler orderEventHandler;

        // 事件声明
        public event OrderEventHandler Order
        {
            add { this.orderEventHandler += value; }
            remove { this.orderEventHandler -= value; }
        }

        public double Bill { get; set; }

        public void PayTheBill()
        {
            Console.WriteLine("I will pay ${0}.", this.Bill);
        }

        public void WalkIn()
        {
            Console.WriteLine("Walk into the restaurant");
        }

        public void SitDown()
        {
            Console.WriteLine("Sit down.");
        }

        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think ...");
                Thread.Sleep(1000);
            }

            if (this.orderEventHandler != null)
            {
                var e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";

                this.orderEventHandler.Invoke(this, e);
            }
        }

        public void Action()
        {
            Console.ReadLine();
            this.WalkIn();
            this.SitDown();
            this.Think();
        }
    }

    public class Waiter
    {
        // 4.事件处理器
        public void Action(Customer customer, OrderEventArgs e)
        {
            Console.WriteLine("I will serve you the dish - {0}.", e.DishName);

            double price = 10;
            switch (e.Size)
            {
                case "small":
                    price *= 0.5;
                    break;
                case "large":
                    price *= 1.5;
                    break;
                default:
                    break;
            }
            customer.Bill += price;
        }
    }
}

事件声明的简略格式

简略格式:一种 filed-like 的声明格式。

filed-like:像字段声明一样 。

简略格式与上例的完整格式只有事件声明事件触发两处不同

using System;
using System.Threading;

namespace EventExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1.事件拥有者
            var customer = new Customer();
            // 2.事件响应者
            var waiter = new Waiter();
            // 3.Order 事件成员 5. +=事件订阅
            customer.Order += waiter.Action;

            customer.Action();
            customer.PayTheBill();
        }
    }

    public class OrderEventArgs : EventArgs
    {
        public string DishName { get; set; }

        public string Size { get; set; }
    }

    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

    public class Customer
    {
        // 简略事件声明,看上去像一个委托(delegate)类型字段
        public event OrderEventHandler Order;

        public double Bill { get; set; }

        public void PayTheBill()
        {
            Console.WriteLine("I will pay ${0}.", this.Bill);
        }

        public void WalkIn()
        {
            Console.WriteLine("Walk into the restaurant");
        }

        public void SitDown()
        {
            Console.WriteLine("Sit down.");
        }

        public void Think()
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Let me think ...");
                Thread.Sleep(1000);
            }

            // 微软的语法糖使得事件变得像委托类型字段一样
            if (this.Order != null)
            {
                var e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                // 事件触发
                this.Order.Invoke(this, e);
            }
        }

        public void Action()
        {
            Console.ReadLine();
            this.WalkIn();
            this.SitDown();
            this.Think();
        }
    }

    public class Waiter
    {
        // 4.事件处理器
        public void Action(Customer customer, OrderEventArgs e)
        {
            Console.WriteLine("I will serve you the dish - {0}.", e.DishName);

            double price = 10;
            switch (e.Size)
            {
                case "small":
                    price *= 0.5;
                    break;
                case "large":
                    price *= 1.5;
                    break;
                default:
                    break;
            }
            customer.Bill += price;
        }
    }
}

事件的本质

事件本质上就是,对委托字段的一个包装器

  • 事件这个包装器对委托字段的访问起限制作用,只让你访问 +=、-= ,让你只能给事件添加或移除事件处理器,让程序更加安全更好维护
  • 事件对外界隐藏了委托实例的大部分功能,仅仅暴露添加/删除事件处理器的功能

使用 EventHandler

C# 中内置一个通用的委托声明:EventHandler
在这里插入图片描述
我们可以将前面代码中自己声明的委托去掉:

public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

将上面这行代码注释后,在 Customer 中使用 EventHandler 作为委托声明:

public class Customer
{
    // 使用默认的 EventHandler,而不是声明自己的
    public event EventHandler Order;
	
	// code..
}

命名约定

在这里插入图片描述
触发事件的方法一般命名为 OnXxx,且访问级别为 protected(自己的类成员及派生类能访问)

依据单一职责原则,把原来的 Think 中触发事件的部分单独提取为 OnOrder 方法

public void Think()
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("Let me think ...");
        Thread.Sleep(1000);
    }

    this.OnOrder("Kongpao Chicken","large");
}

protected void OnOrder(string dishName, string size)
{
    if (this.Order != null)
    {
        var e = new OrderEventArgs();
        e.DishName = dishName;
        e.Size = size;

        this.Order.Invoke(this, e);
    }
}
  • 9
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌宅鹿同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值