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弱事件

原文地址:https://www.cnblogs.com/KevinG/p/3581690.html