精华内容
下载资源
问答
  • C# 委托 (一)—— 委托、 泛型委托与Lambda表达式

    万次阅读 多人点赞 2018-08-19 20:46:47
    1 委托的含义 2 委托声明、实例化和调用 2.1 委托的声明 2.2 委托的实例化 2.3 委托实例的调用 3 泛型委托 3.1 Func委托 3.2 Action委托 3.3 Predicate委托 4 匿名委托 5 Lambda表达式 5.1 表达式Lambda ...

    目录

    1 委托的含义

    2 委托声明、实例化和调用

    2.1 委托的声明

    2.2 委托的实例化

    2.3 委托实例的调用

    3 泛型委托

    3.1 Func委托

    3.2 Action委托

    3.3 Predicate委托

    4 匿名委托

    5 Lambda表达式

    5.1 表达式Lambda

    5.2 语句Lambda


    1 委托的含义

    当需要将一个方法当作另一个方法的参数时,对于某些语言例如C/C++等,需要用函数指针来处理。而对于C#来说,则使用委托机制。

    例如,当我们需要对一个泛型集合ICollection<T>进行排序时,我们定义一个Sort方法,那么这个方法需要哪些参数才能进行排序呢?首先,肯定需要一个Collection<T>对象作为输入参数,代表要排序的对象集合;然后,Sort方法还需要知道如何比较两个对象,经过比较之后才能决定让哪个对象排在前面。因此,Sort方法需要第二个参数,这个参数是一个方法,代表着比较排序对象的方法。这时候这个比较方法参数就只能是一个委托。我们在调用Sort方法对具体的类进行排序时,通过委托传入一个具体类的比较方法。

    再比如,在使用LINQ时,我们经常会用到Where()、Find()等扩展方法,实现对集合中元素的筛选或查找。Where()方法拥有一个Func<T>参数,它就是一个委托。我们调用Where()时需要给这个委托传递一个方法,告诉Where筛选器筛选元素的规则方法。

    委托是一种特殊的类,是一种能够引用方法的类。在创建委托时,就是创建了一个存储方法引用的对象。

    委托是类型安全的。C函数指针只是一个指向一个存储单元的指针,不能保证指向的内容就是正确类型的函数。而对于C#的委托而言,声明一个委托时必须指定返回类型和参数,.NET编译器会严格检查方法的参数和返回类型和委托是否匹配,检查通过后才能进行转换。转换之后的委托实例作为一个参数,传递给调用它的函数。

    一个委托可以被传递任何符合要求的方法。不同场合需要不同方法时,在调用的地方直接将委托参数替换为实际方法就行。因此,委托调用的方法是在程序运行时才能确定的。

    2 委托声明、实例化和调用

    2.1 委托的声明

    前面提到过,委托是一种特殊的类,因此委托的声明与类的声明方法类似,在任何可以声明类的地方都可以声明委托。委托声明用delegate关键字,同时委托要指明方法参数和返回值,写法与方法类似。综合类的声明和方法的声明,委托声明写成如下形式:

    [访问修饰符] delegate 返回值类型 委托名 (形参列表);

    委托的声明实际上是定义了一个派生于System.Delegate类的类,这与一般类的声明语法不同。编译器会根据委托的声明自动创建一个委托的类并实现细节。

    接下来我们以一个简单的List<Student>排序为例进行说明。假设我们有一个Student类,存放学生信息,拥有姓名、年龄和学号三个属性:

    public class Student
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public int Num { get; set; }    
    }

    然后创建一个List<Student>:

    Student s1 = new Student() { Name = "小红", Age = 10, Num = 1001 };
    Student s2 = new Student() { Name = "小华", Age = 9, Num = 1002 };
    List<Student> sList = new List<Student>();
    sList.Add(s1);
    List.Add(s2);

    我们的目标是想给List<Student>对象添加一个排序方法,这个排序方法可以根据年龄或者学号来排序,具体需要哪一种排序需要在客户端调用时指定。(简单起见,本案例中List<Student>只包含两个元素,不纠结于排序算法)

    按照要求,Student对象的比较有两种方法,我们实现两个比较方法,供委托使用:

    //比较年龄
    public static bool Younger(Student s1, Student s2) => s1.Age <= s2.Age;
    
    //比较学号
    public static bool NumSmaller(Student s1, Student s2) => s1.Num <= s2.Num;

    由上,我们可以抽象出一个代表比较Student的方法的委托:

    public delegate bool CompareDelegate(Student first, Student second);

    这个委托的类名为CompareDelegate,注意到委托声明的返回值类型、参数与其代表的方法要完全一致。

    2.2 委托的实例化

    与普通类的使用方法相同,声明了委托之后,我们必须给委托传递一个具体的方法,才能在运行时调用委托实例。委托实例包含了被传递给它的方法的信息,在运行时,调用委托实例就相当于执行它当中的方法。

    委托实例化格式如下:

    委托类名 委托实例名 = new 委托类名(Target) ;

    其中,委托实例名是自定义的名称,Target是要传入的方法的名称。注意,Target是方法的引用,不能带()。带()的话是该方法的调用。区分引用和调用。

    委托的实例化还有一种简单的方法:

    委托类名 委托实例名 = Target;

    在需要委托实例的地方直接传入Target引用即可,C#编译器会自动根据委托类型进行验证,这称为“委托推断”。

    案例:

    //以下两种方法等价
    CompareDelegate myCompareDelegate = new CompareDelegate(Younger);
    CompareDelegate myCompareDelegate = Younger;//委托推断

    2.3 委托实例的调用

    委托实例等价于它当中实际方法,因此可以使用反射的Invoke()方法调用委托实例,也可以直接在委托实例后加上()进行调用。

    我们下面看一下委托所代表的方法是如何被业务方法调用的。这里我们的业务是排序SortStudent方法:

    //使用委托的业务方法
    public static void SortStudent(List<Student> sList, CompareDelegate CompareMethod)
    {
        if (CompareMethod(sList[0], sList[1]))//等价于CompareMethod.Invoke(sList[0],  List[1])
        {
            //sList[0]已经在sList[1]前面了,所以什么也不用做
        }
        else
        {
            sList.Reverse();//交换位置
        }
        //获取排名采用的比较方法的名称
        Console.WriteLine($"\r\n按照 {CompareMethod.Method.Name} 排名:");
        //打印排序后的链表
        foreach (Student s in sList)
            Console.WriteLine($"{s.Name} {s.Age} {s.Num} ");
    }

    这里Sort方法拥有一个CompareDelegate类型的委托实例CompareMethod,它可直接当做具体方法进行调用。

    在客户端对委托进行实例化后,调用SortStudent()方法就可以进行排序了。

    //委托的实例化与使用
    CompareDelegate myCompareDelegate = NumSmaller;//采用比较学号的方法
    SortStudent(sList, myCompareDelegate);
    
    //使用委托推断,与上两行等价
    SortStudent(sList, NumSmaller);

    输出如下:

    按照 NumSmaller 排名:
    小红 10 1001
    小华 9 1002

    3 泛型委托

    我们每次要使用一个委托时,都需要先声明这个委托类,规定参数和返回值类型,然后才能实例化、调用。为了简化这个过程, .NET 框架为我们封装了三个泛型委托类,因此大部分情况下我们不必再声明委托,可以拿来直接实例化使用,方便了我们的日常Coding。

    .这三种泛型委托包括:Func<T>委托、Action<T>委托和Predicate<T>委托。

    3.1 Func<T>委托

    Func<T>委托代表着拥有返回值的泛型委托。Func<T>有一系列的重载,形式如 Func<T1,T2, ... TResult>,其中TResult代表委托的返回值类型,其余均是参数类型。只有一个T时,即Func<TResult>,代表该委托是无参数的。.NET封装了最多16个输入参数的Funct<>委托。

    需要特别注意的是,若方法返回 void ,由于 void 不是数据类型,因此不能定义Func<void>委托。返回 void 的泛型委托见下文的Action<T>。

    Func<T>的使用方法与一般的委托相同。例如上面的案例可改写如下;

    public static void SortStudent(List<Student> sList,Func<Student,Student,bool> CompareMethod)
    {
        if(CompareMethod(sList[0], sList[1]))
        {
        }
        else
        {
            sList.Reverse();
        }
        Console.WriteLine($"\r\n按照 {CompareMethod.Method.Name} 排名:");
        foreach (Student s in sList)
            Console.WriteLine($"{s.Name} {s.Age} {s.Num} ");
    }
    
    //客户端调用
    Func<Student, Student, bool> myCompareFunc = NumSmaller;
    SortStudent2(sList, myCompareFunc);

    注意SortStudent2方法的委托参数也必须是Func<Student, Student, bool>,才能满足方法调用时类型一致的要求。 

    3.2 Action<T>委托

    Action<T>委托代表返回值为空 void 的委托,它也有一些列重载,最多拥有16个输入参数。用法与Func<T>相同。

    3.3 Predicate<T>委托

    这个一般用的较少,它封装返回值为bool类型的委托,可被Func<T>代替。

    4 匿名委托

    采用匿名方法实例化的委托称为匿名委托。

    每次实例化一个委托时,都需要事先定义一个委托所要调用的方法。为了简化这个流程,C# 2.0开始提供匿名方法来实例化委托。这样,我们在实例化委托时就可以 “随用随写” 它的实例方法。

    使用的格式是:

    委托类名 委托实例名 = delegate (args) {方法体代码} ;

    这样就可以直接把方法写在实例化代码中,不必在另一个地方定义方法。当然,匿名委托不适合需要采用多个方法的委托的定义。

    使用匿名方法,以上代码可改写为:

    CompareDelegate anonymousCompare = delegate (Student s3, Student s4) 
    { 
        return s1.Num <= s2.Num; 
    };
    SortStudent(sList, anonymousCompare);

    需要说明的是,匿名方法并不是真的“没有名字”的,而是编译器为我们自动取一个名字。SortStudent方法打印了委托调用的方法的名字(见上文代码),我们可以看到如下输出:

    按照 <Main>b__0 排名:
    小红 10 1001
    小华 9 1002

     编译器为我们的匿名方法取了一个b__0的名字。

    5 Lambda表达式

    江山代有才人出,纵然匿名方法使用很方便,可惜她很快就成了过气网红,没能领多长时间的风骚。如今已经很少见到了,因为delegate关键字限制了她用途的扩展。自从C# 3.0开始,她就被Lambda表达式取代,而且Lambda表达式用起来更简单。Lambda表达式本质上是改进的匿名方法。

    Lambda表达式的灵感可能是来源于数学中的函数表达式,例如下图:

    Lambda表达式把其中的箭头用 => 符号表示。

    如今Lambda表达式已经应用在很多地方了,例如方法体表达式(Expression-Bodied Methods)、自动只读属性表达式等等。

    Lambda表达式形式上分为两种:

    5.1 表达式Lambda

    当匿名函数只有一行代码时,可采用这种形式。例如:

    CompareDelegate LambdaCompare = (s4, s5) => s4.Age <= s5.Age;

    其中=>符号代表Lambda表达式,它的左侧是参数,右侧是要返回或执行的语句。参数要放在圆括号中,若只有一个参数,为了方便起见可省略圆括号。有多个参数或者没有参数时,不可省略圆括号。

    相比匿名函数,在表达式Lambda中,方法体的花括号{}和return关键字被省略掉了。

    其实,上文定义NumSmaller()和Younger()方法时,由于这两个方法主体只有一行代码,所以用的也是表达式Lambda,这是Lambda表达式的推广, 是C# 6 编译器提供的一个语法糖。

    5.2 语句Lambda

    当匿名函数有多行代码时,只能采用语句Lambda。例如,上面的表达式Lambda可改写为语句Lambda:

    CompareDelegate LambdaCompare = (s4, s5) => 
    {
        return s4.Age <= s5.Age;
    };

    语句Lambda不可以省略{}和return语句。

    展开全文
  • 一、委托 设想,如果我们写了一个厨师做菜方法用来做菜,里面有 拿菜、切菜、配菜、炒菜 四个环节,但编写此方法代码的人想让 配菜 这个环节让调用方法的人实现,换句话说,就是想在方法被调用时接收 代码 作为参数...

    一、委托
    设想,如果我们写了一个厨师做菜方法用来做菜,里面有 拿菜、切菜、配菜、炒菜 四个环节,但编写此方法代码的人想让 配菜 这个环节让调用方法的人实现,换句话说,就是想在方法被调用时接收 代码 作为参数,在方法中执行这端传进来的代码。
    但,怎么为一个方法传 代码 进来呢?当然大家想到了传递接口方式来实现,咱先不讨论接口,因为微软为我们提供了一个叫做 【委托】 的类型。

    (一)、委托基础:
    1.先看看代码:
    (1).定一个方法:void SayHi(string name){Console.WriteLine(“Hi~”+name+”! ” );}
    (2).声明一种委托类型:delegate void DGSayHi(string uName);
    (3).创建委托类型对象:DGSayHi dgObj = new DGSayHi(SayHi);//构造函数中传入了方法
    (4).执行委托:
    dgObj(“JamesZou”); //调用委托(奇怪:对象加括号 的方式调用?后面解释。)
    输出:Hi~JamesZou!

    2.什么是委托?
    (1)概念:“C# 中的委托类似于 C 或 C++ 中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后调用该委托对象就可以执行委托对象内方法引用指向的方法,而不必在编译时知道将调用哪个方法(如参数为委托类型的方法,也就是提供了为程序回调指定方法的机制)。”-- 引自MSDN

    (2)通俗:就是一个能存放很多方法的指针的调用清单(但方法签名必须和委托类型签名一样),你一调用这个清单,那么清单里的所有的指针所对应的方法就会依次被执行。

    (3)比方说:有三台机器A、C、D,点一个红色按钮就会运行。操作人员接到指令,要求在接到电话后分别打开AD机器,然后然后工人就在接到电话后,先后打开AD机器。(此例中的 三台机器就是方法,操作员,就可以看成是“委托”啦)

    (4)概要图例:
    DGSayHi dgObj = new DGSayHi(SayHi);
    dgObj(“James”); //调用委托对象,就会执行委托对象里的方法。

    3.委托有什么用?
    A.能够帮程序员在需要时,根据条件动态执行多个方法:(接上例代码)
    (1)定三个方法:
    void SayHi(string name){Console.WriteLine(“Hi~”+name ); }
    void DaZhaoHu(string name){ Console.WriteLine(“你好啊~”+name ); }
    string OHaUo(string name){ Console.WriteLine(“OHaUo ~”+name ); return “JapHi”;}

    (2)创建委托类型对象,并通过构造函数传参方式向委托对象“注册”第一个方法:
    DGSayHi dgObj = new DGSayHi(SayHi);

    (3)继续“注册两个方法”:
    dgObj+=DaZhaoHu;// (奇怪:对象之间用+=符号来操作?后面解释)
    //dgObj+=OhaUo;//注释此行代码,因为编译时报错,OhaUo方法签名与委托类型的签名不一致(委托签名无返回值)。

    (4)执行委托对象:
    dgObj(“James”); //执行了此委托中注册的两个方法
    输出:
    Hi~James
    你好啊~James

    (5)概要图例

    B. 委托作为方法参数(回调方法机制)
    (1).接上例代码,再定义一个方法:
    void DoTestDelegateFun(DGSayHi dgObj){dgObj(“钢铁侠”);}
    (2).调用此方法:
    DoTestDelegateFun(SayHi);//输出:Hi~钢铁侠(奇怪:竟然直接传方法了?后面解释)

      C.委托语法糖
        (1).注意到上面有3个地方我们都觉得“奇怪”:
        a.调用委托对象dgObj(“JamesZou”);
        b.向委托注册方法 dgObj+=DaZhaoHu;
        c.将方法作为参数 DoTestDelegateFun(SayHi);
        这些用法其实都是FW为我们提供的简便语法(它们有个可爱的名字:语法糖),在编译时由编译器转成完整的代码:
        a. dgObj.Invoke(“JamesZou”);
        b. dgObj = (DGSayHi) Delegate.Combine(dgObj, new DGSayHi(this.DaZhaoHu));//Combine方法将第二个参数,添加到dgObj中,并返回委托对象。
        c. this.DoTestDelegateFun(new DGSayHi(this.SayHi));
        Delegate类、Invoke方法、Combine方法是哪来的呢?
    

    (二)、委托原理
    1.delegate 关键字
    (1).概念:delegate 关键字用于声明一个引用类型,该引用类型可用于封装命名方法或匿名方法。
    (2).编译后生成的的中间代码。
    请大家思考一下,关键字是类型吗?不是。那编译器遇到这个关键字做了什么事情?借助【IL反汇编程序】 我们来看一看:
    a.开始-程序-如图:

    b.打开项目文件夹下的binDebug文件夹,找到程序集 CodeForFun.exe,拖入到【IL反汇编程序】界面中便可看到程序集的IL代码:
    找到我们定义了委托DGSayHi的类DelegateForFun,发现,里面的 委托类型声明 代码
    编译前:delegate string DGSayHi(string uName);
    变成了一个类:

    单击展开后我们再来看看:

    看出什么了?
    (I).继承了System.MulticastDelegate。
    (II).包含了构造方法、BeginInvoke、EndInvoke、Invoke方法。
    也就是说此时,delegate代码已经编译成了如下代码:
    编译后:
    class DGSayHi:System.MulticastDelegate
    {
    public DelegateForFun();
    void Invoke(string value);
    IAsyncResult BeginInvoke(string value,AsyncCallback callback,Object object);
    void EndInvoke(IAsyncResult result);
    }
    (3)System.MulticastDelegate 类
    下面我们来看看借助.Net Reflector工具来查看类库中的 MulticastDelegate 类
    public abstract class MulticastDelegate : Delegate
    由此我们可以看出继承关系:DGSayHi –> MulticastDelegate –> Delegate
    MulticastDelegate类中有3个重要的成员,其中两个继承自 Delegate :

    a.三者的作用:
    _methodPtr 里保存的就是 方法指针。
    _target 里用来保存方法所在的对象。
    _invocationList 其实使用时是个object数组,在注册多个方法时,其他方法就保存在此成员中,而它也就是 委托链 的关键容器。

    b.概要图:

    图中的委托对象 dgObj 在创建时创建了指向方法 SayHi的指针并保存在 _methodPtr中;_target中保存了SayHi方法所在的类的对象(比如我把这段代码写在窗体里按钮的点击方法中,那么此时 _target就是 SayHi方法所在的窗体对象);_invocationList 中保存了追加的两个方法的指针,但这两个方法指针都是分别被装在 MuticastDelegate对象中。

    展开全文
  • 目录一、委托的定义二、委托的声明1、声明委托2、在该类中定义方法,参数返回值与声明的委托对应3、委托的实例化与调用4、委托的本质三、委托的应用场景四、代码地址 一、委托的定义 将方法当作参数传递,就是委托。...

    一、委托的定义

    将方法当作参数传递,就是委托。

    二、委托的声明

    委托类似于方法,可以有参数和返回值,但是没有方法体(但是委托的本质不是一个方法),需要用delegate修饰,可以在类的内部声明,也可以在类的外部声明。

    1、声明委托

    namespace Lanyp.Course.Delegate
    {
        #region delegate out of class 
        /// <summary>
        /// 定义在类的外部的无参数无返回值的委托
        /// </summary>
        public delegate void NoReturnNoParaOutClass();
        #endregion
    
        /// <summary>
        /// 自定义委托
        /// </summary>
        public class CoustomDelegate
        {
            #region delegate in class
            /// <summary>
            /// 无参数无返回值
            /// </summary>
            public delegate void NoReturnNoPara();
            /// <summary>
            /// 有参数无返回值
            /// </summary>
            public delegate void NoReturnHasPara(int x, int y);
            /// <summary>
            /// 无参数有返回值
            /// </summary>
            public delegate string HasReturnNoPara();
            /// <summary>
            /// 有参数有返回值
            /// </summary>
            public delegate int HasReturnHasPara(out int x, ref int y);
            #endregion
            
    		
            }
    }
    

    2、在该类中定义方法,参数返回值与声明的委托对应

    #region private methods
    /// <summary>
    /// 无参无返回值的函数
    /// </summary>
    private void NoReturnNoParaMethod()
    {
        Console.WriteLine("无参数无返回值的方法");
    }
    
    /// <summary>
    /// 有参数无返回值的函数
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    private void NoReturnHasParaMethod(int x, int y)
    {
        Console.WriteLine($"{x}+{y}={x + y}");
    }
    
    /// <summary>
    /// 无参数有返回值的函数
    /// </summary>
    private string HasReturnNoParaMethod()
    {
        return "无参数有返回值的方法";
    }
    
    /// <summary>
    /// 有参数有返回值的函数
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    private int HasReturnHasParaMethod(out int x, ref int y)
    {
        x = 2;
        return x + y;
    }
    #endregion
    

    3、委托的实例化与调用

    • 委托的实例化
      委托的实例化类似与类的实例化,如NoReturnNoPara noReturnNoPara = new NoReturnNoPara(NoReturnNoParaMethod);,但是区别在于需要将一个方法当作参数传递过去,需要注意的是,传过去的方法的参数与返回值必须与声明的委托一致。
    • 执行委托的两种方式:
      (1)调用Invoke函数:noReturnNoPara.Invoke();
      (2)直接调用实例化对象,如noReturnNoPara();
    #region public methods
    /// <summary>
    /// 主方法
    /// </summary>
    public void Show()
    {
        Console.WriteLine("=========================无参无返回值的委托====================");
        {
            //1、委托可以实例化,在实例化的过程中需要将一个方法当作参数传递过去
            NoReturnNoPara noReturnNoPara = new NoReturnNoPara(NoReturnNoParaMethod);
            //2、执行委托的两种方式
            //noReturnNoPara.Invoke(); //方式一
            noReturnNoPara();//方式二
            //noReturnNoPara.BeginInvoke(null,null); //分配一个线程去执行方法,多线程中使用
            //noReturnNoPara.EndInvoke(null);//多线程中使用
        }
    
        Console.WriteLine("=========================有参数无返回值的委托====================");
        {
            //3、实例化的时候要求传递进来的方法结构与委托一致:要求参数和返回值与委托保持一致
            //NoReturnHasPara noReturnHasPara = new NoReturnHasPara(NoReturnNoParaMethod); //错误示范
            NoReturnHasPara noReturnHasPara = new NoReturnHasPara(NoReturnHasParaMethod); //正确示范
            //noReturnHasPara.Invoke(1, 3);//方式一
            noReturnHasPara(1, 3);//方式二
    
        }
    
        Console.WriteLine("=========================无参数有返回值的委托====================");
        {
            HasReturnNoPara hasReturnNoPara = new HasReturnNoPara(HasReturnNoParaMethod);
            string result = hasReturnNoPara.Invoke();
            Console.WriteLine(result);
        }
    
        Console.WriteLine("=========================有参数有返回值的委托====================");
        {
            HasReturnHasPara hasReturnHasPara = new HasReturnHasPara(HasReturnHasParaMethod);
            int x = 0;
            int y = 3;
            int result = hasReturnHasPara.Invoke(out x, ref y);
            Console.WriteLine($"{x}+{y}={x + y}");
        }
        Console.ReadKey();
    }
    

    4、委托的本质

    委托的本质是一个类,继承自MulticastDelegate特殊类
    在这里插入图片描述

    三、委托的应用场景

    思考:有两个学生,分别是北京人和上海人,他们的方言不同,如何实现不同地区的同学向别人打招呼?

    • 方案一:利用枚举,优势在于没有冗余代码,但是缺点在于每次新增地名都需要修改枚举和方法,造成代码不稳定,耦合程度高
    • 方案二:每次新增一个地方就新增一个方法:相对于方案一优势在于不用修改太多的代码,耦合低,稳定性更高,缺点在于冗余代码太多
    /// <summary>
    /// 如果需要实现不同的地区的同学打招呼,有以下两种常规方案
    /// </summary>
    public class SayHi
    {
        #region 方案一:利用枚举,优势在于没有冗余代码,但是缺点在于每次新增地名都需要修改枚举和方法,造成代码不稳定,耦合程度高
        public enum From
        {
            Shanghai,
            Beijing
        }
    
        /// <summary>
        /// 利用枚举约束,使调用者尽量少出错
        /// </summary>
        /// <param name="name"></param>
        /// <param name="from"></param>
        public void SayAction(string name, From from)
        {
            Console.WriteLine("招招手~~~~");
            switch (from)
            {
                case From.Shanghai:
                    Console.WriteLine($"{name}:侬好!");
                    break;
                case From.Beijing:
                    Console.WriteLine($"{name}:你好!");
                    break;
                default:
                    throw new Exception("From type was wrong!");
            }
        }
        #endregion
    
        #region 方案二:每次新增一个地方就新增一个方法:相对于方案一优势在于不用修改太多的代码,耦合低,稳定性更高,缺点在于冗余代码太多
        public void ShangHai(string name)
        {
            Console.WriteLine("招招手~~~~");
            Console.WriteLine($"{name}:侬好!");
        }
        
        public void Beijing(string name)
        {
            Console.WriteLine("招招手~~~~");
            Console.WriteLine($"{name}:你好!");
        }
        #endregion
    }
    

    那么有没有更好的解决方案呢?

    • 终极解决方案:行为设计模式,利用委托传进来不同的方法,执行不同地区的学生的打招呼的方式
    • 这样做的优点:
      1、增加公共业务逻辑方便,只需要在公共方法内部增加即可,既减少代码的冗余,又最大程度保证程序的稳定
      2、增加一个新的业务逻辑模块(新增一个地区的人打招呼),逻辑由调用者(Student)提供,可以做到逻辑解耦
    /// <summary>
    /// 委托
    /// </summary>
    /// <param name="name"></param>
    public delegate void SayHiDelegate(string name);
    
    /// <summary>
    /// 扩展方法
    /// </summary>
    public static class StudentExtension
    {
    	#region 调用者提供的业务逻辑
        public static void ShangHai(this Student student, string name)
        {
            Console.WriteLine($"{name}:侬好!");
        }
    
        public static void HongKong(this Student student, string name)
        {
            Console.WriteLine($"{name}:雷猴!");
        }
    
        public static void Beijing(this Student student, string name)
        {
            Console.WriteLine($"{name}:你好!");
        }
    	#endregion
    	
    	#region 公共方法
        /// <summary>
        /// 终极解决方案:行为设计模式,利用委托传进来不同的方法,执行不同地区的学生的打招呼的方式
        /// 优势:
        ///   1、在该方法中可以增加公共业务逻辑,可以减少代码的冗余
        ///   2、增加一个新的业务逻辑模块(新增一个地区的人打招呼),逻辑由调用者提供,可以做到逻辑解耦
        /// </summary>
        /// <param name="student"></param>
        /// <param name="name"></param>
        /// <param name="sayHiDelegate"></param>
        public static void Action(this Student student, string name, SayHiDelegate sayHiDelegate)
        {
            Console.WriteLine("招招手~~~");
            sayHiDelegate.Invoke(name);
        }
        #enregion
    }
    

    调用时,只需要将各个不同地区的打招呼方式的扩展方法传递到委托中即可

    {
        //上海人打招呼
        Console.WriteLine("======================上海人打招呼====================");
        Student student = new Student()
        {
            Name = "张三"
        };
        SayHiDelegate studentDelegate = new SayHiDelegate(student.ShangHai);
        student.Action(student.Name, studentDelegate);
    }
    {
        //北京人打招呼
        Console.WriteLine("======================北京人打招呼====================");
        var student = new Student()
        {
            Name = "王五"
        };
        var studentDelegate = new SayHiDelegate(student.Beijing);
        student.Action(student.Name, studentDelegate);
    }
    
    

    结论:

    • 如果在工作中的代码耦合严重,公共逻辑与业务逻辑之间本应该分开,可以使用委托来解耦
    • 如果代码冗余,可以使用委托来减少冗余代码。

    四、代码地址

    C#高级编程之委托

    展开全文
  • C#中的委托和事件

    2021-11-15 14:04:10
    我相信全网关于委托和事件的文章和概述,大家应该已经读过很多篇。但是就我的观察来看,大多数文在讲述这方面概念时,都会用烧开水和狗叫主人的例子来讲述事件怎么工作,这样比喻固然与生活联系紧密,但看多了难免有...

    图片

    写在最前

    我相信全网关于委托和事件的文章和概述,大家应该已经读过很多篇。但是就我的观察来看,大多数文在讲述这方面概念时,都会用烧开水和狗叫主人的例子来讲述事件怎么工作,这样比喻固然与生活联系紧密,但看多了难免有一些审美疲劳。

    所以今天,我打算结合自己的一些工作经历,再来谈谈我个人对委托和事件的理解,希望能带给大家一些不一样的见解。

    图片

    先谈概念

    委托:一种引用类型,表示具有特定参数列表和返回类型的方法的引用。在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。你可以通过委托实例调用方法。委托用于将方法作为参数传递给其他方法。事件处理程序就是通过委托调用的方法。

    事件:类或对象可以通过事件向其他类或对象通知发生的相关事情。发送(或引发)事件的类称为“发布者”,接收(或处理)事件的类称为“订阅者”。

    以上概述来自MSDN官方文档:

    https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/delegates/

    从概念中我们其实已经可以看出,委托主要是对方法的一种引用,而事件则充当了多个类或者对象进行相互通知的桥梁。

    如果我这么解释你可以明白的话,那么我们今天的主题就已经明朗了,下面我们就用具体的代码实例来讲述。

    图片

    再看例子

    委托

    我们需要先声明一个委托实例,在C#中,显示声明自定义委托采用delegate关键字,声明方式与声明普通方法相同,需要指定访问范围和返回类型,同时包含访问参数。

    同时我们针对委托,声明对应的方法,方法的返回值和参数需要与委托保持一致,若不一致则会在委托传递方法时出现编译错误。

    委托执行内部传递方法的方式是使用Invoke方法,此处需注意,C#中同时提供了BeginInvoke和EndInvoke的方法对,用于异步执行内部的方法

    下面我们一起来看一下示例:

    在此示例中,我们分别定义了一个无参数无返回值的委托和一个包含2个参数并返回int类型的委托,分别用于执行两种对应的方法。在两个委托执行对应的Invoke方法之后,会产生以下的结果:

    图片

    结果和我们预期一致,程序同步顺序地执行了两个委托并打印出相应的结果。但是看到这里也许你会有一个疑问,既然委托执行时的结果与直接调用方法一致,那么我们为什么还需要使用委托来执行方法呢?

    这时我们就要回到最初的定义:委托用于将方法作为参数传递给其他方法

    由于实例化的委托是一个对象,因此可以作为参数传递或分配给一个属性。这允许方法接受委托作为参数并在稍后调用委托。这被称为异步回调,是在长进程完成时通知调用方的常用方法。当以这种方式使用委托时,使用委托的代码不需要知道要使用的实现方法。功能类似于封装接口提供的功能。

    我们一起使用一个比较直观的例子来验证:

    using System;
    
    namespace ConsoleApp1
    {
        class Program
        {
            public delegate void Del(string message);
            
            static void Main(string[] args)
            {
                Del handler = new Del(DelegateMethod);
                MethodWithCallback(1,2,handler);
                Console.Read();
            }
    
            public static void DelegateMethod(string message)
            {
                Console.WriteLine(message);
            }
    
            public static void MethodWithCallback(int param1, int param2, Del callback)
            {
                callback(string.Format("当前的值为:{0}", (param1 + param2)));
            }
        }
    }

    在这段代码中,我们声明了一个无返回值委托Del,用于接收传入的消息,并且该委托指向了一个调用控制台的方法DelegateMethod。而后续我们调用MethodWithCallback方法时,无需调用控制台相关方法,而是直接将Del委托的实例作为参数传入,就实现DelegateMethod方法的调用。这个实例就是我们上述提到的异步回调和委托对方法的引用。

    运行结果如下:

    图片

    此处我使用了JetBrains出品的IDE Rider,因此截图界面会与VS有所不同,喜欢轻量级IDE的同学可以试试这款,有30天的免费试用期,地址:Rider: The Cross-Platform .NET IDE from JetBrains,此处不再过多讲解。

    根据我们上述的实例讲解,大家对委托的作用及使用场景应该有了初步的理解,但是我们仔细想一想,上述的场景似乎少了些什么,为什么我们的委托始终只能指向一个方法进行调用呢?

    这里就要引出我们接下来的概念:多播委托。

    事实上,委托是可以调用多个方法的,这种方式就叫做多播委托,在C#中,我们可以使用+=的运算符,将其他委托附加到当前委托之后,就可以实现多播委托,相关示例如下:

    using System;
    
    namespace ConsoleApp1
    {
        class Program
        {
            public delegate void Del();
            
            static void Main(string[] args)
            {
                Del handler = new Del(DelegateMethod1);
                Del handlerNew = new Del(DelegateMethod2);
                handler += handlerNew;
                handler.Invoke();
                Console.Read();
            }
    
            public static void DelegateMethod1()
            {
                Console.WriteLine("天才第一步!");
            }
    
            public static void DelegateMethod2()
            {
                Console.WriteLine("天才第二步!");
            }
        }
    }

    ​​​​​​​在这个示例中,我们重新编写了一个方法叫DelegateMethod2,同时我们又声明了一个新的委托对象handlerNew指向该方法。接着我们使用+=的方式将handlerNew添加至handler并执行该委托,得到的结果如下:

    图片

    如我们先前所料,多播委托把多个方法依次进行了执行。此时如果某个方法发生异常,则不会调用列表中的后续方法,如果委托具有返回值和/或输出参数,它将返回上次调用方法的返回值和参数。与增加方法相对应,若要删除调用列表的方法,则可以使用-=运算符进行操作。

    关于委托的理解与常用方式,我们就讲解到这里,事实上,多播委托常用于事件处理中,由此可见,事件与委托有着千丝万缕的联系,下面我们就拉开事件的序幕。

    事件

    如前文讲解时所说,事件是一种通知行为,因此要分为事件发布者和事件订阅者。而且在.Net中,事件基于EventHandler委托和EventArgs基类的,因此我们在声明事件时,需要先定义一个委托类型,然后使用event关键字进行事件的定义。

    相关的示例如下:

    using System;
    
    namespace ConsoleApp1
    {
        public class PublishEvent
        {
            public delegate void NoticeHandler(string message);
    
            public event NoticeHandler NoticeEvent;
    
            public void Works()
            {
                //触发事件
                OnNoticed();
            }
    
            protected virtual void OnNoticed()
            {
                if (NoticeEvent != null)
                {
                    //传递事件及参数
                    NoticeEvent("Notice发布的报警信息!");
                }
            }
        }
        
        public class SubscribEvent
        {
            public SubscribEvent(PublishEvent pub)
            {
                //订阅事件
                pub.NoticeEvent += PrintResult;
            }
            
            /// <summary>
            /// 订阅事件后的响应函数
            /// </summary>
            /// <param name="message"></param>
            void PrintResult(string message)
            {
                Console.WriteLine(string.Format("已收到{0}!采取措施!",message));
            }
        }
        
        class Program
        {
            static void Main(string[] args)
            {
                PublishEvent publish = new PublishEvent();
                SubscribEvent subscrib = new SubscribEvent(publish);
                //触发事件
                publish.Works();
                Console.Read();
            }
        }
    }

    ​​​​​​​从事例中我们可以看出,我们分别定义了发布者和订阅者相关的类。

    在发布者中,我们需要声明一个委托NoticeHandler,然后定义一个此类型的事件NoticeEvent。在定义对象之后,我们需要对事件进行执行,因此有了OnNoticed方法,此方法只用于事件本身的执行。那么什么时候才能执行事件呢?于是我们又有了触发该事件的方法Works,当Works方法被调用时,就会触发NoticeEvent事件。

    而在订阅者中,我们需要对NoticeEvent事件进行订阅,因此我们需要发布者的对象PublishEvent,同时需要对它的事件进行订阅。正如我们前文所说,订阅使用+=的方式,与多播委托的使用是一致的,而+=后的对象正是我们需要响应后续处理的方法PrintResult。当事件被触发时,订阅者会接收到该事件,并自动执行响应函数PrintResult。

    执行结果如下图所示:

    图片

    从执行结果中我们可以看出,在事件被触发后,订阅者成功接收到了发布者发布的事件内容,并进行自动响应,而我们在此过程中从未显式调用订阅者的任何方法,这也是事件模型的本质意义:从发布到订阅。

    在微软官方文档中提到,事件是一种特殊的多播委托,只能从声明它的类中进行调用。客户端代码通过提供对应在引发事件时调用的方法的引用来订阅事件。这些方法通过事件访问器添加到委托的调用列表中,这也是我们可以使用+=去订阅事件的原因所在,而取消事件则和多播委托一致,使用-=的方式。

    图片

    最后总结

    本文我们讲解了委托和事件之间的关系,以及它们的使用场景。在我个人的工作经历中,曾经有3年左右的时间从事C/S相关的开发工作,其中包含了大量多线程、委托和事件的使用场景。主要用于在开发WinForm程序时,不同窗体(包含父子窗体)之间进行相互通信,其中都是基于事件的发布和订阅作为实现的。而委托的使用场景则更多,很多C#方法在使用时都会传入一个Action或者Func委托作为参数,而这些参数同时又支持Lambda表达式,这就又引出了匿名函数的概念,由于篇幅所限,此处不再进行讲解,大家可以自行搜索资料进行了解和学习。

    展开全文
  • 摘要:声明和使用有这样一个应用场景,如果系统有异常,需要及时...添加代码如下,如果不知道日志功能的可以参考【Log4Net日志记录的实现】//声明一个通知的委托publicdelegatevoidNoticeEventHander(stringmessage)...
  • C# 委托&线程的见解(上)—— 委托

    千次阅读 2018-05-12 16:37:17
    对于委托,我们都知道他是一个引用类型,具有引用类型所具有的通性。委托就相当于叫人帮忙,让它帮你做一些事情。 委托一般包含三个方法,分别是BeginInvoke、EndInvoke、Invoke。EndInvoke就不必说了,一看就知道是...
  • 声明委托1.1实例化委托1.2委托调用1.3委托使用场景1.4系统内置委托Action Func1.5 多播委托二、什么是事件1.声明事件1.1事件调用1.3事件使用场景1.4 事件与设计模式 前言 提示:这里可以添加本文要记录的大概内容:...
  • C# 委托与事件的区别

    2021-08-09 21:47:49
    1.什么是委托委托可以把一个方法代入另一个方法,相当于指向函数的指针,换句话说,委托相当于一个函数指针,而事件就相当于保存委托的数组。 总的来说,委托是一个类,该类内部维护着一个字段,指向一个方法。 ...
  • 废话少说,快上代码C# 干货全解_qinze5857的博客-CSDN博客 二、实例 DegtThd.cs namespace wpfConsoleApp { public class DegtThd { //在线程的cs文件中声明一个委托 public delegate void ...
  • C#委托+回调详解

    千次阅读 2019-08-28 14:19:37
    今天写不完,明天会接着写的,,,, ...前面的一篇文章,函数指针,其实是为这个做铺垫的,说白了委托就相当于C语言中的函数指针,具体说用委托有什么好处,我也不知道,但是因为你只要学C#那么回调就一定要会,回调是委托...
  • # 什么是委托 1、从数据结构来讲,委托是和类一样是一种用户自定义类型。 2、委托是方法的抽象,它存储的就是一系列具有相同参数和返回类型的方法的地址。调用委托的时候,委托包含的所有方法将被执行。 # 委托...
  • 一、C# 委托、事件、回调 基础概念 1.委托C#中实现事件、回调的基础 2. 二、
  • C#委托和java里的委托

    千次阅读 2018-11-01 18:23:45
    C#委托是方法的代理,委托的是方法,当调用委托时就调用了它的方法,是一类行为的抽象,是一种自定义数据类型。它有统一的返回类型和参数。1.定义委托:访问级别delegate 返回值 委托的名称(参数组成)delegate ...
  • 简单说一下委托的含义 委托就是可以将别的类中的方法直接传递到委托所在类中使用的方法,具体操作步骤如下。 搭建界面如下: 具体操作如下//form1全部代码 namespace 委托 { public partial class Form1 : Form {...
  • c# 委托

    2021-02-27 15:23:27
    c#委托# 系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 ...
  • 什么是委托  首先要知道什么是委托,用最通俗易懂的话来讲,你就可以把委托看成是用来执行方法(函数)的一个东西。如何使用委托  在使用委托的时候,你可以像对待一个类一样对待它。即先声明,再实例化。只是有点...
  • c#委托

    2021-04-20 23:06:48
    C#语言中的委托是函数指针的升级版。 Action 和 Func 是 C# 内置的委托实例 using System; namespace DelegateExample { class Program { static void Main(string[] args) { Calculator calculator = new ...
  • C#委托

    2020-12-28 22:47:51
    初级委托详解(C#委托有什么好处? 它起什么作用?) 什么是委托  首先要知道什么是委托,用最通俗易懂的话来讲,你就可以把委托看成是用来执行方法(函数)的一个东西。 如何使用委托  在使用委托的时候,...
  • C#事件与委托详解

    2021-06-27 15:40:00
    C#中的事件概括来说有五个组成部分,分别是: 1、事件的拥有者(Event Source,对象) 2、事件(Event,成员) 3、事件的响应者(Event Subscribe,对象) 4、事件的处理器(Event Handler,成员)——本质上是...
  • C#委托技术有很多作用,以下是一个简单的实例完成C#委托解决不同对象之间的传值或者方法调用的问题: 假设想想实现以下功能: 从主窗体中创建2个子窗体,想把2个子窗体的信息发送到主窗体中,并在主窗体中显示,在子...
  • C#同步委托与异步委托学习总结

    千次阅读 2021-12-01 15:54:34
    C#异步委托与同步委托学习总结
  • c#委托(Delegates)--基本概念及使用

    万次阅读 多人点赞 2018-05-13 00:10:56
    在我这菜鸟理解上,委托就是可以用方法名调用另一方法的便捷方法,可以简化switch等语句的重复。最近做项目的时候恰好需要用到委托,便来复习及学习委托的使用。嗯...本人以前并没有用过,只是稍微知道而已。以下是...
  • C# 委托与事件总结

    2021-12-11 21:27:26
    C# 中的委托和事件可以说是超级拦路虎了,一个不小心就容易让人直接放弃学习。最近几天花了点时间钻研了一下,查看了一些资料,希望做个总结。 我还达不到技术大佬的级别,主要就是做个**自我总结**,很推荐大家看...
  • C#高级--委托详解

    2021-08-14 17:59:23
    C#高级–委托详解 一、委托是什么 1、委托是什么 ​ 委托和类一样是一种用户自定义类型,它存储的就是一系列具有相同签名和返回类型的方法的地址,调用委托的时候,它所包含的所有方法都会被执行。 2、委托声明 (1...
  • using System;using System.Collections.Generic;using System.Linq;using System.Text;...namespace ConsoleApplication委托为什么没有返回值{class Program{static void Main(string[] args){Publ...
  • 委托:本质上讲委托是一种可以将方法作为参数的方法,可以包含参数也可以不包含参数 事件:从本质上将也是委托,只不过是进行了封装的委托。事件可以绑定方法,绑定之后就会监视这些方法,当达到事件的触发条件,就...
  • 本文主要介绍委托、事件和Lambda表达式。 文章目录1. 委托1.1 委托使用1.2 委托链2. 事件3. Lambda表达式3.1 委托进化史3.2 Lambda方法体4. 参考资料 1. 委托 1.1 委托使用 委托是一种类型,表示对具有特定参数列表...
  • 闲来无事,看到有争论c#事件机制效率的,小小地测试一番~ 结论先放这里: 1.订阅者数量较少的情况下和接口列表访问不相伯仲,事件机制访问速度多次比列表快 2.订阅者较多的情况(百万数量级)下,触发事件消耗的...
  • 参考:C#利用委托实现子窗体调用父窗体的方法 子窗口中的代码: 代码原理:声明一个委托,定义一个委托成员变量,这里仅是定义,是空的,还没有赋值,在父窗口里赋值;最后一句代码则是使用委托去做具体的事情了...
  • C# 委托刷新

    2021-10-26 14:49:52
    #委托刷新 1、在弹出界面中定义委托类的方法,并调用方法创建委托对象。 public partial class FrmForkLend : Form { //定义委托刷新 public delegate void Reload(object sender, EventArgs e); public Reload ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 78,865
精华内容 31,546
关键字:

c#委托的作用

c# 订阅