C#基础知识点总结(六)- 委托Lambda事件

本文围绕C#展开,介绍了委托、lambda表达式和事件。委托是方法的抽象,可存储方法地址,有声明使用、多播等特性,还有Action<T>、Func<T>和Predicate<T>等类型;lambda表达式可将实现代码赋予委托,能访问外部变量;事件基于委托,提供发布/订阅机制。

1. 委托(delegate)

委托和类一样是一种用户自定义类型,委托时方法的抽象,它存储的就是一系列具有相同签名和返回类型的方法的地址,调用委托的时候,它所包含的所有方法都会被执行。

委托:把方法传送给其他方法。要传递方法,就必须把方法的细节封装在一种新的对象类型中,委托包含的是一个或多个方法的地址

1.1 声明和使用委托

使用委托经过两个步骤:

1. 定义要使用的委托,告诉编译器这种类型的委托表示哪种类型的方法,定义委托时,必须给出他所表示的方法的签名和返回类型等全部细节:返回类型+委托类型名+参数列表

2. 必须创建该委托的一个或多个实例,编译器在后台将创建表示该委托的一个类

        //声明了一个委托,该方法带有一个int参数,并返回void
        delegate void ShowData(int x);
        //该委托有两个long型参数,返回类型为double
        delegate double ToLongData(long first, long second);
        //在委托的定义上应用常见的访问修饰符:public,private,protected
        public delegate string GetAString();

        //声明委托变量,把委托看作一个类,声明委托变量就像声明类对象
         ShowData data1, data2;

委托的特点:

1. 给定委托的实例可以引用任何类型的任何对象上的实例方法或静态方法,只要方法的签名匹配委托的签名即可

2. 与类一样,委托类型必须在被用来创建变量以及类型对象之前声明

3. 把委托看作类一样,声明委托变量就像声明类的对象一样,在初始化委托变量的时候也要使用new运算符。当创建委托时,传递到 new 语句的参数就像方法调用一样书写,但是不带有参数。

4. 委托在编译的时候确实会编译成类。因为 Delegate 是一个类,所以在任何可以声明类的地方都可以声明委托

        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }

        public static int MultNum(int q)
        {
            num *= q;
            return num;
        }
        public static int getNum()
        {
            return num;
        }
        static void Main(string[] args)
        {
            // 其中包含作为调用列表中的第一个成员的方法的名字。方法可以是实例方法或静态方法。
            //声明委托变量,把委托看作一个类,声明委托变量就像声明类对象 
            //只传方法名 NumberChanger(AddNum)
            NumberChanger nc1 = new NumberChanger(AddNum);
            NumberChanger nc2 = new NumberChanger(MultNum);
            // 使用委托对象调用方法
            nc1(25);
            //输出35
            Console.WriteLine("Value of Num: {0}", getNum());            
            nc2(5);
            //nc1(25)调用完后num变成了35,这里35*5为175
            Console.WriteLine("Value of Num: {0}", getNum());
            Console.ReadKey();
        }
delegate double ProcessDelegate(double a, double b); // 定义一个委托。

static double Multiply(double a, double b)
{ return a * b; }

static double Divide(double a, double b)
{ return a / b; }

static double Sum(double c, double d)
{ return c + d; }

public static void Main(string[] args)
{
    ProcessDelegete MyDelegate;
    //=也是调用委托,和MyDelegate = new ProcessDelegate(Multiply)效果一样
    MyDelegate =  Multiply;
}
public delegate void DoSomeThing();
public class MrZhang {
    public static void HelpWith(DoSomeThing callback) {
        // 小张的唯一方法,帮别人做某事;
        Console.WriteLine("小张帮====》");
        callback();
    }
}

public class MrMing
{
    public static void BuyTicket() {
        Console.WriteLine("小明买了一张火车票");
    }
    public static void BuyMovieTicket() {
        Console.WriteLine("小明买了一张电影票");

    }
    public static void Main() {
        // 定义两件事,是小明需要去做的事
        DoSomeThing thing1 = new DoSomeThing(BuyTicket);
        DoSomeThing thing2 = new DoSomeThing(BuyMovieTicket);
        // 让小张去做这两件事
        thing1 += thing2;
        MrZhang.HelpWith(thing1);
        
        Console.ReadKey();
    }
}

//输出结果:

小张帮====》
小明买了一张火车票
小明买了一张电影票

1.2 委托的多播

委托对象可使用 "+" 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。"-" 运算符可用于从合并的委托中移除组件委托。

1. 多个委托能够串行运行,这里的 + 号被重载了

2. 第一次用的“=”,是赋值的语法;第二次,用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。

        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }

        public static int MultNum(int q)
        {
            num *= q;
            return num;
        }
        public static int getNum()
        {
            return num;
        }
        static void Main(string[] args)
        {
            NumberChanger nc;
            NumberChanger nc1 = new NumberChanger(AddNum);
            NumberChanger nc2 = new NumberChanger(MultNum);
            //第一次用的“=”,是赋值的语法;第二次,用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。
            nc = nc1;
            nc += nc2;
            //可简化为以下两行, 等价于nc = nc1;nc += nc2
            NumberChanger delegate1 = new NumberChanger(nc1);
            delegate1 += nc2;

            // 调用多播,这里nc(5)先是调用了nc1方法,返回值为15;再接着用返回值15调用了nc2方法,15*5返回值为75
            nc(5);
            Console.WriteLine("Value of Num: {0}", getNum());
            Console.ReadKey();
        }

1.3 Action<T>、Func<T>和Predicate<T>委托

有返回值就用Func,无返回值就用Action

1.3.1 Action<T>

泛型Action<T>委托表示引用一个void返回类型的方法,这个委托类存在不同的变体,可以传递至多16种不同的参数类型。没有参数的Action类可调用没有参数的方法,比如Action<in T1, in T2, in T3>是调用了三个带参数的方法

            //无参的Action<T>
            Action nonePara;
            nonePara = Racer.Add;
            //无参的调用
            nonePara();
            nonePara.Invoke();

            //带两个参数的Action<T>
            Action<string, int> twoPara;
            //必须也匹配带string和int两个参数的方法,才可以调用
            twoPara = Racer.Sum;
            twoPara("This a", 888);
            twoPara.Invoke("This a", 888);

            //用lambda表达式来执行委托
            Racer.LambdaAction(() => { Console.WriteLine("Lambda的Action<T>方法"); });
            //可以调用无参的Action
            Racer.LambdaAction(Racer.Add);

1.3.2 Func<T>

Func<T>允许调用带返回类型的方法。它也存在不同的变体,可以传递至多16个参数类型和一个返回类型。Func<out TResult>委托类型可以调用带返回类型且无参数的方法,Func<in T, out TResult>调用带一个参数的方法,Func<in T1, in T2, in T3, out TResult>调用带3个参数的方法

1. 参数最后一位就是返回值类型(返回值的类型和Func输出参数类型相同, out TResult)

       public static string Sum()
        {
            Console.WriteLine("无参数的Func<T>方法");
            return "aa";
        }
        public static int Sum(int act)//参数 Action
        {
            Console.WriteLine("一个参数的Func<T>方法");
            return 0;

        }
        public static string Sum(string str, int num)
        {
            Console.WriteLine($"两个参数的Func<T>方法: {str} + {num}");
            return str;
        }


                    //无参的Func返回一个string,Sum没有参数,但是返回类型是string 
            Func<string> noneParaAndResult = Racer.Sum;
            //执行这个无参返回类型是string的委托
            noneParaAndResult();
            noneParaAndResult.Invoke();

            //Func<int, int>是一个Int参数和返回类型是Int
            Func<int, int> OneParaAndResult = Racer.Sum;
            //执行这个一个Int参数和返回类型是Int的Sum函数
            OneParaAndResult(1);
            OneParaAndResult.Invoke(2);

            Func<string, int, string> twoParaAndResult = Racer.Sum;
            twoParaAndResult("111", 1);
            twoParaAndResult.Invoke("222", 2);

1.3.3 Predicate<T>

predicate 是返回bool型的泛型委托,只能接受一个传入参数

            public static bool SelectString(string str)
            {
                return str.Contains("e");
            }
            //Predicate<string>表示传入参数是string(只能有一个传入参数),返回bool的委托
            Predicate<string> pre = SelectString;
            var names = new string[]
            {
                "apple",
                "blue",
                "cat"
            };
            var result = Array.FindAll(names, pre);
            //结果为apple;blue
            Console.WriteLine($"结果为{string.Join(";", result)}");

1.4 匿名方法(C#3.0后使用lambda表达式替代,了解即可)

匿名方法是用作委托的参数的一段代码,用匿名方法定义委托的语法和普通定义没什么区别,但是在实例化委托时,就会出现区别。

1. 匿名方法外部的跳转语句不能跳到该匿名方法的内部

2. 在匿名方法内部不能访问不安全的代码

3. 不能访问在匿名方法外部使用的ref和out参数,但可以使用在匿名方法外部定义的其他变量

4. 如果需要用匿名方法多次编译同一个功能,就不要是用匿名方法,可写一个命名方法,然后引用

            string mid = ",middle part,";
            //这里delegate (string param){}部分是匿名方法的使用
            Func<string, string> anonDel = delegate (string param)
            {
                param += mid;
                param += "and this is a string";
                return param;
            };

2. lambda表达式

lambda作用是把实现代码赋予委托,只要有委托参数类型的地方,就可以使用lambda表达式

1. lambda运算符“=>”的左边列出了需要的参数,右边定义了赋予lambda变量的方法的实现代码

2. lambda表达式有几种定义参数的方式,如果只有一个参数,只写出参数名就可以了。

3. 委托使用了多个参数,就把这些参数名放在圆括号中。为了方便起见,可以在圆括号中给变量名添加参数类型,如果编译器不能匹配重载后的版本,那么使用参数类型可以帮助找到匹配的委托

            //lambda表达式使用了参数s,Func<string, string>定义了一个string参数和一个string返回类型,所以这里s类型是string
            Func<string, string> onePara = s => $"change uppercase {s.ToUpper()}";
            Console.WriteLine(onePara("test"));

            //委托使用了多个参数,就把这些参数名放在圆括号中,这里参数x和y是double类型
            Func<double, double, double> twoParams = (x, y) => x * y;
            Console.WriteLine(twoParams(2, 3));

            //为了方便起见,可以在圆括号中给变量名添加参数类型,如果编译器不能匹配重载后的版本,那么使用参数类型可以帮助找到匹配的委托
            Func<double, double, double> twoParaWithTypes = (double x, double y) => x * y;
            twoParaWithTypes(4, 5);

4. lambda只有一条语句,使用就像上面的代码一样就可以。但是如果lambda包含多条语句,就必须添加花括号和return语句

            //para是string类型,lambda有多条语句,要包含{}和return
            Func<string, string> lambadMutiLine = param =>
            {
                param += "aaa";
                param += "THIS IS A";
                return param;
            };

2.1 闭包

闭包:lambda表达式可以访问lambda表达式块外部的变量

            int a = 6;
            int b = 8;

            Func<int, int, int> para = (x, y) =>
            {
                
                Console.WriteLine($"x + y = {x+y}");
                return x + y;
            };
            //上面的lambda表达式使用了lambda表达式外部的a和b变量
            //x + y = 14
            para(a, b);


            //在lambda表达式外的a和b变量发生了变化,那么lambda表达式也重新使用a和b的新值
            a = 2;
            b = 3;
            //x + y = 5
            para(a, b);

3. 事件

事件基于委托,为委托提供了一种发布/订阅机制。

事件是在委托类型变量前加上event关键字,其本质是用来对委托类型的变量进行封装,类似于类的属性对字段的封装

事件相当于增强了委托的封装性,以保证委托类型的变量在类外部不能被直接调用。这样相当于无论是在类的内部声明public还是protected的委托类型变量,只要用事件event进行封装,其效果都相当于声明了一个私有的委托类型变量

3.1 声明事件

第一步: 在类的内部声明事件,首先必须声明该事件的委托类型

第二步: 然后,声明事件本身,使用 event 关键字

    //定义猫叫委托
    public delegate void CatCallEventHandler();
    public class Cat
    {
        //定义猫叫事件
        public event CatCallEventHandler CatCall;
        public void OnCatCall()
        {
            Console.WriteLine("猫叫了一声");
            CatCall?.Invoke();
        }
    }
    public class Mouse
    {
        //定义老鼠跑掉方法
        public void MouseRun()
        {
            Console.WriteLine("老鼠跑了");
        }
    }
    public class People
    {
        //定义主人醒来方法
        public void WakeUp()
        {
            Console.WriteLine("主人醒了");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat();
            Mouse m = new Mouse();
            People p = new People();
            //关联绑定 
            cat.CatCall += new CatCallEventHandler(m.MouseRun);
            cat.CatCall += new CatCallEventHandler(p.WakeUp);
            cat.OnCatCall();

            Console.ReadKey();
        }
    }

事件注意细节:

1. 事件只能使用+=来将函数注册到其上,使用-=来将函数注销

2. 事件的使用一般通过发布者和订阅者来进行。发布者会在某一条件下触发某事件,订阅者可以通过订阅该事件,来对该事件的触发做出反应。其类似于广播->接收的模型

详细可看:C# 委托(Delegate) | 菜鸟教程 (runoob.com)

C# 事件(Event) | 菜鸟教程 (runoob.com)

C# 中的委托和事件(详解) - SkySoot - 博客园 (cnblogs.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值