• C#高编


    摘要:

    • 委托
    • Lambda表达式
    • 事件

    1.委托是寻址方式的.NET版本。与C++的函数指针区别:委托时类型安全的类,定义了返回类型和参数的类型。

    委托是一种特殊类型的对象,特殊之处在于,我们之前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。

    2.声明委托

    • 定义要使用的委托,即告诉编译器这种类型的委托表示哪种类型的方法。(编译器在后台将创建表示该委托的类)
    • 创建该委托的一个或多个实例 。
    delegate void IntMethodInvoker(int x);//必须提供方法的签名和返回类型等细节,类型安全性非常高

    定义委托可以在定义类的任何相同地方定义,可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在名称空间中把委托定义为顶层对象。

    委托派生自基类System.MulticastDelegate,基类又派生自System.Delegate

    与类不同的是,创建类的实例称为“对象”,而创建的委托的实例仍称为委托,必须从上下文确定其含义。

    3.使用委托

    在C#中,委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。

    private delegate string GetString();
    static void Main()
    {
        int x = 40;
        GetAString firstStringMethod = new GetAString(x.ToString);//ToString()是实例方法(不是静态),因此需要指定实例x和方法名来正确地使用委托
        ...
    }

    使用委托实例的名称,并通过括号中的等效参数来调用委托

    Console.WriteLine("String is {0}",firstStringMethod());
    //等价于
    //Console.WriteLine("String is{0}",x.ToString());

    也可以使用firstStringMethod.Invoke();来调用,是一样的。

    委托推断:为了减少输入量,只要需要委托实例,可以只传送地址。

    GetAString firstStringMethod = x.ToString;

    委托推断在需要委托实例的任何地方使用,也可以用于事件。

    使用委托的另外一种方式是:把方法组合到一个数组中,这样可以在循环中调用不同的方法。

    delegate double DoubleOp(double x);
    static void Main()
    {
        DoubleOp[] operations = {MathOperations.MultipleByTwo,MathOperations.Square};
        ...
    }

    4.Action<T>和Func<T>委托

    Action<T>委托表示引用一个void返回类型的方法。因为这个委托类存在不同的变体,所以可以传递至多16种不同的参数类型。

    • Action<int T1,int T2>:调用带两个参数的方法

    Func<T>委托允许调用带返回类型的方法,与Action<T>类型,Func<T>也定义了不同的变体,至多也可以传递16种不同的参数类型和一个返回类型。

    • Func<out TResult>:调用带返回类型且无参数方法
    • Func<int T,out TResult>:调用带一个参数的方法

    示例使用Func<Int T,out TResult>委托

    delegate double DoubleOp(double x);
    
    Func<double,double>[] operations = {MathOperations.MultipleByTwo,MathOperations.Square};

    那么调用时方法应为

    private static void ProcessAndDisplayNumber(Func<double, double> action, double value)
    {
         double result = action(value);
         Console.WriteLine("Value is {0},result of operation is {1}", value, result);
    }

    冒泡排序案例体会委托的用途:

    /// <summary>
    /// 冒泡排序的泛型版本
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sortArray"></param>
    /// <param name="comparison"></param>
    static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
    {
         bool swapped = true;
         do
         {
              swapped = false;
              for (int i = 0; i < sortArray.Count - 1; i++)
              {
                   if (comparison(sortArray[i + 1], sortArray[i]))
                   {
                       T temp = sortArray[i];
                       sortArray[i] = sortArray[i + 1];
                       sortArray[i + 1] = temp;
                       swapped = true;
                    }
               }
          } while (swapped);
    }

    参数2实则委托,定义了函数签名,即包含连个参数且返回值为布尔类型,本质上传递的是一个函数指针。

    5.多播委托

    定义:包含多个方法的委托,可按顺序连续调用多个方法,为此委托的签名就必须返回void,否则只能得到委托调用的最后一个方法的结果。

    可以使用返回类型为void的Action<T1>委托:

    Action<double> operations = MathOperations.MultiplyByTwo;
    operations += MathOperations.Square;

    多播委托可以识别运算符“+”和“+=”,还识别运算符“-”和“-=”,以从委托中删除方法调用。

    注意:

    • 多播委托对调用方法链的顺序并未正式定义,因此应避免编写依赖于以特定顺序调用方法的代码。
    • 多播委托调用的其中一个方法抛出异常,整个迭代就会停止。

    为了避免第二个问题应自己迭代方法列表,如:

    static void Main()
    {
        Action d1 = One;
        d1 += Two;
        
        Delegate[] delegates = d1.GetInvocationList();//返回委托数组
        foreach(Action d in delegates)
        {
            try
            {
                d();
            }
            catch(Exception)
            {
                Console.WriteLine("Exception caught");
            }
        }
    }

    6.匿名方法

    另外一种使用委托的方法,是用作委托的参数的一段代码。

    string mid = ",middle part,";
    
    Func<string,string> anonDel = delegate(string param)
    {
        param += mid;
        param += " and this was added to the string.";
        return param;
    };
    Console.WriteLine(anonDel("Start of string"));

    匿名方法的优点:减少代码,不必定义由委托使用的方法,有助于降低代码的复杂性,特别是定义事件时。

    匿名方法的缺点:代码执行的不太快,编译器仍定义了一个方法,只是名称是自动指定的。当需多次编写功能相同的方法,则不建议使用。

    必须遵循的规则:

    • 在匿名方法中不能使用跳转语句(break、goto或continue)跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该方法内部。
    • 在匿名方法内部不能访问不安全的代码。也不能访问在匿名方法外部使用的ref和out参数,但可以访问方法外部定义的其他变量。

    7.Lambda表达式

    从C#3.0开始,可以使用Lambda替代匿名方法。

    将上面示例修改为Lambda语法:

    string mid = ",middle part,";
    
    Func<string,string> anonDel = param =>
    {
        param += mid;
        param += " and this was added to the string.";
        return param;
    };
    Console.WriteLine(anonDel("Start of string"));

     单个参数和多个参数定义方式:

    Func<string,string> oneParam = s => String.Format("change uppercase {0}",s.ToUpper());//单参
    
    Func<double,double,double> TwoParams = (x,y) => x*y;//多参
    
    Func<double,double,double> twoParamsWithTypes = (double x,double y) => x*y;//添加参数类型

    注意:如果Lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,因为编译器会添加一条隐式的return语句。

    Lambda表达式外部的变量:

    通过Lambda表达式可以访问Lambda表达式外部的变量,但是要比较小心,特别是通过另一个线程调用Lambda时,我们可能不知道进行了这个调用,也不知道当前外部变量的值是什么。

    编译器其实在定义Lambda表达式时创建了一个匿名类,外部变量通过构造函数传递进来。

    8.事件

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

    namespace Wrox.ProcSharp.Delegates
    {
        //事件发布程序
        public class CarInfoEventArgs : EventArgs
        {
            public CarInfoEventArgs(string car)
            {
                this.Car = car;
            }
            
            public string Car{get;private set;}
        }
    
        public class CarDealer
        {
            public event EventHandler<CarInfoEventArgs> NewCarInfo;
    
            public void NewCar(string car)
            {
                Console.WriteLine("CarDealer, new car{0}", car);
                if(NewCarInfo != null)
                {
                    NewCarInfo(this, new CarInfoEventArgs(car));
                }
            }
        }
    
        //事件监听器
        public class Consumer
        {
            private string name;
            
            public Consumer(string name)
            {
                this.name = name;
            }
    
            public void NewCarIsHere(object sender,CarInfoEventArgs e)
            {
                Console.WriteLine("{0}:car{1} is new",name,e.Car);
            }
        }
    
        //订阅
        class Program
        {
            static void Main()
            {
                var dealer = new CarDealer();    
                
                var michael = new Consumer("Michael");
                dealer.NewCarInfo += michael.NewCarIsHere;
    
                dealer.NewCar("Mercedes");
            }
        }
    }
    View Code

    作为一个约定,事件一般使用带两个参数的方法:

    • 第一个参数是一个对象,包含事件的发送者
    • 第二个参数提供了事件的相关信息,随不同的事件类型而不同。

    对于EventHandler<TEventArgs>,第一个参数是object类型,第二个参数是T类型,还有一个关于T的约束:必须派生自基类EventArgs

    public event EventHandle<TEventArgs>(object sender,TEventArgs e) where TEventArgs:EventArgs

    定义事件的简化记法,编译器自动创建委托变量

    public event EventHandler<CarInfoEventArgs> NewCarInfo;

    完整记法

    private delegate EventHandler<CarInfoEventArgs> newCarInfo;
    public event EventHandler<CarInfoEventArgs> NewCarInfo
    {
        add
        {
            newCarInfo += value;
        }
        remove
        {
            newCarInfo = value;
        }
    }

    与多播委托一样,方法调用顺序无法保证,如果要控制调用,需使用Delegate类的GetInvocationList()方法显式调用。

    8.1弱事件

  • 相关阅读:
    Linux添加系统环境变量
    keras 或 tensorflow 调用GPU报错:Blas GEMM launch failed
    python 安装虚拟环境
    Seq2Seq 到 Attention的演变
    聊天内容处理笔记
    LSTM 详解
    keras 打印模型图
    zip 的对象是不能用索引去取的
    c# 反射获取属性值 TypeUtils
    .iml文件恢复
  • 原文地址:https://www.cnblogs.com/KevinG/p/3581690.html
Copyright © 2020-2023  润新知