Logo

C# 委托和事件

photo

2024年01月17日

· 委托 delegate 

  委托是一种类。把方法当做参数传给另一个方法。

  可以理解为 c/c++ 函数指针的升级版。(声明方式相同,为了照顾可读性和延续 c/c++ 传统)

  委托的简单实用:Action【无返回值】 和 Func【有返回值】(C# 类库自带)

  自定义委托:public delegate…(一般不用,Action 和 Func 足以)

  委托优点:复用。

  委托缺点:可读性下降,不好维护。使用不当可能会造成内存泄漏和程序性能下降。 

  应该适时的使用接口 interface 取代一些对委托的使用(Java完全地使用接口取代了委托的功能)

//声明一个带参数且无返回值的委托
public delegate void SayHelloDlg(string name);

//声明一个泛型委托
public delegate T MyDele<T>(T t1, T t2);

class Program
{
    //声明一个SayHelloDlg类型的事件
    public static event SayHelloDlg SayHelloEvent;

    static void Main(string[] args)
    {
        //委托
        SayHelloDlg dlg = SayHello;
        dlg += SayGoodBye;  //委托链
        dlg -= SayGoodBye;
        dlg("老王");

        //泛型委托
        var deleAdd = new MyDele<int>(Add);
        Console.WriteLine(deleAdd(100, 200));
        var deleSub = new MyDele<double>(Sub);
        Console.WriteLine(deleSub(100.1, 200));

        //Action
        var action = new Action<string>(SayHello);
        action("老A");

        //Func
        var func1 = new Func<int, int, int>(Add);
        Console.WriteLine(func1(100, 200));
        var func2 = new Func<double, double, double>(Sub);
        Console.WriteLine(func2(100, 200));

        //匿名函数
        SayHelloDlg dlg1 = delegate (string name)
        {
            Console.WriteLine($"{name},我是匿名函数!");
        };
        dlg1("老王");

        //Lamda = inline + 匿名函数
        SayHelloDlg dlg2 = (name) =>
        {
            Console.WriteLine($"{name},我是lamda语句!");
        };
        dlg2("老王");
        //Lamda + Action
        Action<string> action1 = (name) =>
        {
            Console.WriteLine($"{name},我是lamda语句!");
        };
        action1("老张");
        //Lamda + Func + 泛型委托
        DoSomeCalc((x, y) => { return x + y; }, 200, 300);

        Console.ReadKey();
    }

    private static void SayHello(string name)
    {
        Console.WriteLine($"{name},你好!");
    }

    private static void SayGoodBye(string name)
    {
        Console.WriteLine($"{name},再见!");
    }

    private static int Add(int arg1, int arg2)
    {
        return arg1 + arg2;
    }

    private static double Sub(double arg1, double arg2)
    {
        return arg1 - arg2;
    }

    static void DoSomeCalc<T>(Func<T, T, T> func, T x, T y)
    {
        var res = func(x, y);
        Console.WriteLine(res);
    }
}

案例

public delegate int Transformer(int x); //委托类型
public delegate int Transformer2(int x);
public delegate T Transformer3<T>(T arg);

class Util
{
    public static void Transform<T>(T[] values, Transformer3<T> t3)
    {
        foreach (var i in values)
        {
            t3(i);
        }
    }
    public static void Transform2<T>(T[] values, Func<T, T> t4)
    {
        foreach (var i in values)
        {
            t4(i);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        //所有的委托类型都派生于System.MuticastDelegate,而它又派生于System.Delegate
        //调用者调用委托,然后委托调用目标方法,实现解耦。
        //方法是在运行时才赋值给委托变量的
        #region 委托实例
        Transformer t = new Transformer(Square); //委托变量t
        t(3); //t.Invoke(3)
        #endregion

        //使用+=或-=操作符时,实际上是创建了新的委托实例,并把它赋给当前的委托变量
        //C#会把作用于委托的+=、-=操作编译成使用Syste.Deltegate的Combine和Remove两个静态方法。
        #region 多播委托
        Transformer2 t2 = null;
        t2 += Square;
        t2 += Cube;
        t2 -= Square;
        t2(3);
        Console.WriteLine(t2.Method);
        #endregion

        #region 泛型委托类型
        int[] values = { 1, 2, 3 };
        Util.Transform(values, Square);
        #endregion

        #region Func 和 Action 委托
        // delegate TResult Func<out TResult>();
        // delegate TResult Func<in T, out TResult>(T arg);
        // delegate TResult Func<in T, int T2, out TResult>(T arg, T2 arg2);
        // ..T16
        // delegate void Action();
        // delegate void Action<in T>(T arg);
        // delegate void Action<in T,in T2>(T arg, T2 arg2);
        // ..T16
        //真省事!不用单独定义委托类型了
        Util.Transform2(values, Square);
        #endregion

        //委托可以解决的问题,接口都可以解决。
        //什么时候使用委托而不是接口:
        //  需要多播能力,而接口只能定义一个方法
        //  订阅者需要多次实现接口
    }

    private static int Square(int x)
    {
        var result = x * x;
        Console.WriteLine(result);
        return x * x;
    }
    private static int Cube(int x)
    {
        var result = x * x * x;
        Console.WriteLine(result);
        return result;
    }
}

委托、多播委托、泛型委托、Func和Action委托类型

 

· 事件 event

  角色:事件是一种使对象或类能够提供通知的成员。

  使用:用于对象或类之间的动作协调与信息传递(消息推送)

  事件多用于桌面、手机等开发的客户端编程,因为这些程序大部分是用户通过事件来驱动的。

  MVC、MVP、MVVM 等模式,是事件模式更高级、更有效的玩法。

  日常开发中,使用已有事件的机会比较多,自己声明事件的机会比较少。

  事件是基于委托,依赖于委托。只不过被限制了用法,防止滥用委托。如果需要提供给别人调用就封装成事件。所以事件的本质是委托字段的包装器,对委托字段的访问起限制作用,对外界仅暴露添加/移除事件处理器的功能( += 或 -= ),避免了使用.Invoke方法调用事件。 

 

事件模型的五个组成部分:事件的拥有者、事件成员、事件的响应者、事件处理器、事件订阅。  

 

 

事件应用的常见模式

 

class Program
{
    static void Main(string[] args)
    {
        Timer timer = new Timer();
        timer.Interval = 1000;
        Boy boy = new Boy();
        Girl girl = new Girl();
        timer.Elapsed += boy.Action;
        timer.Elapsed += girl.Action;
        timer.Start();
        Console.ReadLine();
    }
}

class Boy
{
    internal void Action(object sender, ElapsedEventArgs e)
    {
        Console.WriteLine("Boy!");
    }
}

class Girl
{
    internal void Action(object sender, ElapsedEventArgs e)
    {
        Console.WriteLine("Girl!");
    }
}

案例一

class Program
{
    static void Main(string[] args)
    {
        Form form = new Form();
        Controller controller = new Controller(form);
        form.ShowDialog();
    }
}

class Controller
{
    private Form form;
    public Controller(Form form)
    {
        if (form != null)
        {
            this.form = form;
            this.form.Click += this.FormClicked;
        }
    }

    private void FormClicked(object sender, EventArgs e)
    {
        this.form.Text = DateTime.Now.ToString();
    }
}

案例二

 

class Program
{
    static void Main(string[] args)
    {
        MyForm form = new MyForm();
        form.Click += form.FormClicked;
        form.ShowDialog();
    }
}

class MyForm : Form
{
    internal void FormClicked(object sender, EventArgs e)
    {
        this.Text = DateTime.Now.ToString();
    }
}

案例三

class Program
{
    static void Main(string[] args)
    {
        MyForm form = new MyForm();
        form.ShowDialog();
    }
}

class MyForm : Form
{
    private TextBox textBox;
    private Button button;
    public MyForm()
    {
        this.textBox = new TextBox();
        this.button = new Button();
        this.Controls.Add(this.button);
        this.Controls.Add(this.textBox);
        this.button.Click += this.ButtonClicked;
    }
    internal void ButtonClicked(object sender, EventArgs e)
    {
        this.textBox.Text = DateTime.Now.ToString();
    }
}

案例四(常用)

 

自定义事件

class Program
{
    static void Main(string[] args)
    {
        Customer customer = new Customer();
        Waiter waiter = new Waiter();
        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 OrderEnventHandler(Customer customer, OrderEventArgs e);

public class Customer
{
    private OrderEnventHandler orderEventHandler;

    public event OrderEnventHandler Order
    {
        add
        {
            this.orderEventHandler += value;
        }
        remove
        {
            this.orderEventHandler -= value;
        }
    }
    public double Bill { get; set; }

    public void Think()
    {
        if (this.orderEventHandler != null)
        {
            OrderEventArgs e = new OrderEventArgs();
            e.DishName = "Kongpao Chicken";
            e.Size = "large";
            this.orderEventHandler.Invoke(this, e);
        }
    }

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

    public void Action()
    {
        this.Think();
    }
}

class Waiter
{
    public void Action(Customer customer, OrderEventArgs e)
    {
        Console.WriteLine("Waiter: I will serve you the dish - {0}.", e.DishName);
        double price = 10;
        switch (e.Size)
        {
            case "small":
                price = price * 0.5;
                break;
            case "large":
                price = price * 1.5;
                break;
            default:
                break;
        }

        customer.Bill += price;
    }
}

自定义事件的完整声明实例

class Program
{
    static void Main(string[] args)
    {
        Customer customer = new Customer();
        Waiter waiter = new Waiter();
        customer.Order += waiter.Action;
        customer.Action();
        customer.PayTheBill();
    }
}

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

public class Customer
{
    public event EventHandler Order;

    public double Bill { get; set; }

    public void Think()
    {
        this.OnOrder("Kongpao Chicken", "large");
    }

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

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

    public void Action()
    {
        this.Think();
    }
}

class Waiter
{
    public void Action(object sender, EventArgs e)
    {
        Customer customer = sender as Customer;
        OrderEventArgs orderInfo = e as OrderEventArgs;
        Console.WriteLine("Waiter: I will serve you the dish - {0}.", orderInfo.DishName);
        double price = 10;
        switch (orderInfo.Size)
        {
            case "small":
                price = price * 0.5;
                break;
            case "large":
                price = price * 1.5;
                break;
            default:
                break;
        }

        customer.Bill += price;
    }
}

自定义事件的简洁声明实例

 

如果这个委托是为了声明事件而准备的,为增强代码可读性,那么委托名称以 EventHandler结尾。事件名称 EventArgs结尾。

用于声明事件的委托类型的命名约定:
    用于声明Foo事件的委托,一般命名为FooEventHandler(除非是一个非常通用的事件约束)
    FooEventHandler委托的参数一般有两个:
    第一个是object类型,名字为sender,实际上就是事件的拥有者、事件的source。
    第二个是EventArgs类的派生类,类名一般为FooEventArgs,参数名为e。也就是前面讲过的事件参数。
    虽然没有官方的说法,但我们可以把委托的参数列表看做是事件发生后发送给事件响应者的“事件消息”
    触发Foo事件的方法一般命名为OnFoo,即“因何引发”、“事出有因”
    访问级别为protected,不能为public,不然又成了可以“借刀杀人”了
事件的命名约定:
    带有时态的动词或者动词短语
    事件拥有者”正在做”什么事情,用进行时;事件拥有“做完了”什么事情,用完成时

 

学习地址:刘铁锰对委托和事件的详解(强烈推荐)

本文为原创文章,请注意保留出处!
C# 消息队列 RabbitMQ 2024年01月17日

1.引言RabbitMQ——RabbitMessageQueue的简写,但不能仅仅理解其为消息...C#消息队列RabbitMQ

插件化项目中,遇到这样一个需求,每个插件或者每个方法一个日志文件,方便后期错误排查源码地址:...log4.net自定义日志文件名称

热门文章

修复群晖Synology Drive client右键菜单缺失问题 本教程主要解决windows10右键菜单中没有SynologyDrive菜单的问题,整体思路是找到...修复群晖SynologyDriveclient右键菜单缺失问题 作者:Pastore Antonio
1827 浏览量
docker如何查看一个镜像内部的目录结构及其内部都有哪些文件 前言:有时候我们会在docker上下载一个镜像,或者是上传一个镜像到docker上,甚至有时候就是在...docker如何查看一个镜像内部的目录结构及其内部都有哪些文件 作者:Pastore Antonio
1808 浏览量
Adobe Acrobat Pro 激活 这里记录了一些AdobeAcrobat的激活教程和组件。浏览量:1,688 作者:Pastore Antonio
1535 浏览量
configure: error: Package requirements (oniguruma) were not met configure:error:Packagerequirements(oniguruma)...configure:error:Packagerequirements(oniguruma)werenotmet 作者:Pastore Antonio
1535 浏览量
追寻日出,找回自己 为什么我要去追寻日出?其实我是一个很懒的人,每次都起不来,直到有一次我在租房中睡到了大天亮,阳光照...追寻日出,找回自己 作者:Pastore Antonio
1515 浏览量