C#基础知识1:C#中的委托、匿名函数Lambda表达式与回调

委托的使用

首先,应该明确,CLR提供委托机制就是提供了回调函数机制

  • 使用书中比较形象的例子:
    • 遗嘱(遗嘱的发起人已经没有能力执行,委托律师执行):遗嘱中有一系列指令:“付账单、捐善款、其他留给猫”这样的指令
    • 然后律师则会根据这个指令执行对应的执行办法

在C#中,回调函数这个机制更多的被用在了事件中,经常被用到的EventHandler的本质就是一个委托。

委托都用来干嘛

  1. 相比C、C++提供的回调只能看到一个函数指针(无法直接得知函数的入参和返回值),C#的回调机制是类型安全的
  2. 委托被主要用来处理事件,实现C、C++中的函数回调机制,例如List.FindAll(Predicate match)
  3. 在LINQ中,通常被视为数据管道的一部分,接受输入并返回结果,或者判断某项是否符合当前筛选器
  4. C#2引入了泛型委托、匿名方法,C#3引入了Lambda表达式

委托的基本用法

使用委托的四个基本条件

(注意委托类型和委托实例的不同)

(1)声明委托类型 (2)必须有一个对应委托类型的方法且包含了要执行的代码 (3)必须创建一个委托实例 (4)必须调用(invoke)委托实例

  • 声明委托类型

    委托类型实际上只是一个表明了方法的返回值,以及参数列表的样子
// 表明了一个返回值为void,并且入参为一个string类型的方法
delegate void StringProcessor(string str);
// 或者使用更加通用的命令方式来体现委托的声明
delegate void ProgressOneStringParamaterVoidFunc(string str);
// 或者一个可以通过C#协变性的特点,接受更大范围内的回调函数
delegate object ProgressAnyType(stream anystream);

StringProcessor实际上是一个类型,它有方法,可以创建它的实例,并将引用传递给实例

  • 必须有一个方法与委托声明的返回值、参数列表一致

    这个方法的返回值以及函数签名都必须与所声明的委托一致才行
  • 例如一个静态类型的方法
// 一个符合委托声明的函数实现;
static void PrintString(string str)
{
    Console.WriteLine("PrintString:" + str);
}
  • 再例如一个实例类型的方法
class ClassChapter2Instance
{
    public void PrintStringInstance(string str)
    {
        Console.WriteLine("PrintStringInstance:" + str);
    }
}
  • 利用协变性原理,string继承自object,FileStream继承自Stream,所以,以下这个函数可以引用到ProgressAnyType委托当中
public string GetFileStreamReturnString(FileStream fs)
{
    // string ret;
    // ……
    return ret;
}
  • 必须创建一个委托实例

    有了委托声明(类似于委托书模板)、委托实现(能够执行该委托的办法),这样就可以创建委托类型的实例(一个实际的委托书)了
static void Main(string[] args)
{
    // 创建委托类型的实例;
    StringProcessor printstring;
    // 创建委托实例,使用!!!静态方法!!!;
    printstring = new StringProcessor(Program.PrintString);

    // 创建委托类型的实例;这个对象被称为操作的目标
    StringProcessor printstringinstance;
    // 创建委托实例,使用!!!实例方法!!!;
    ClassChapter2Instance C2 = new ClassChapter2Instance();
    printstringinstance = new StringProcessor(C2.PrintStringInstance);

}
  • 调用(invoke)委托实例

    调用就很简单了,已经把具体的方法给了对应的委托实例了,这样直接使用这个委托实例就可以实现调用了(Invoke)
printstring("Delegate~printstring");

// 调用这个委托实例
printstringinstance("Delegate~printstringinstance");
}
  • C#2、C#3中的进一小步:使用微软提供给我们的语法糖,匿名函数和Lambda表达式

    匿名函数的标准形式::delegate(显示类型参数列表) { 实现代码 }
    Lambda表达式的标准形式(看起来和匿名函数相比,少了个delegate关键字,多了个=>符号)

Lambda表达式的简化形式:即省略了括号,花括号,return关键字

delegate(显示类型参数列表) { 实现代码 }

Lambda表达式:
标准形式:(显示类型 参数列表) => { 实现代码 }
简化形式:参数名 => 表达式

其中,=>是C#3.0新增的,告诉编译器正在使用Lambda表达式,更加明确的理解,可以将这个Lambda表达式理解为:参数变成了代码的结果

例子A:最简单的做到了简化代码量的作用

delegate void StringProcessor(string str);

StringProcessor Func;
// 以下这三个是等价的:
Func = delegate(string TextArg) { return; };
Func = (string arg) => { return; };
Func = TextArg => return;

委托的本质

C#的委托,本质上就是一种类型,所以使用它的时候需要用到new关键字。
所以,实例化出来的委托提供了如下几个平时能经常用到的方法供我们使用:

  1. Invoke,就是直接调用所引用的方法
  2. BeginInvoke:可以异步处理,并且通过AsyncCallback返回结果
  3. EndInvoke:可以获取异步处理的结果
  4. GetInvocationList:获取当前委托实例的所有委托引用

应用了委托的实例

应用场景:使用委托实现观察者模式

有一个这样的场景:有一个后台线程一直在监听某一个地址上的消息,一旦监听到它所需要的消息,就会发送一个通知消息,给所有在它这里注册的对象,暂且称这个消息为Trap(这个是SNMP网络协议中的叫法)。那么对于这个“观察者“来说,观察者类的设计如下:

public delegate void UpdateTrap(string stringlist);

public class GetMessage
{
    public static Thread WaitforTrap = new Thread(WaitTrap);                   // 执行Trap接收的线程;
    private static List<UpdateTrap> TrapNodifyList = new List<UpdateTrap>();   // 观察者列表;
    private static volatile bool WaitTrapRunstate = true;                      // 是否接收Trap的标志位;
    private static Socket socket;

    /// 
    /// 设置观察者列表;
    /// 
    /// 需要被添加的观察者
    public static void SetNodify(UpdateTrap obj)
    {
        TrapNodifyList.Add(obj);
        
        // 如果是第一次设置SetNodify,则启动Trap监听线程;
        if (TrapNodifyList.Count == 1)
        {
            TrapMessage.WaitforTrap.Start();
        }
    }

    /// 
    /// 统一调用观察者的Update方法;
    /// 
    /// 
    static public void Nodify(List<string> Ret)
    {
        foreach (UpdateTrap update in TrapNodifyList)
        {
            update(Ret);
        }
    }

    /// 
    /// 接收Trap信息;
    /// 
    static private void WaitTrap()
    {
        // 建立一个Socket实例,接收所有网口的IP地址,并绑定***端口; 
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        socket.Bind(ep);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 0);
        
        string Ret = "";

        // 一直等待Socket返回结果
        while (WaitTrapRunstate)
        {
            byte[] data = new byte[16 * 1024];
            int length = -1;
            IPEndPoint peer = new IPEndPoint(IPAddress.Any, 0);
            EndPoint inep = (EndPoint)peer;
            
            try
            {
                inlen = socket.ReceiveFrom(data, ref inep);
                // ~~~解析~~~
                Ret = "解析结果";
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception {0}", ex.Message);
                inlen = -1;
            }

            // 如果inlen大于0则证明接收到Trap;
            if (inlen > 0)
            {
                // 通知在观察者处注册了的实例;
                Nodify(Ret);
            }
            else
            {
                if (inlen == 0)
                    Console.WriteLine("Zero length packet received.");
            }

        }
        Console.WriteLine("Trap waiting thread: terminating gracefully.");
    }

    public static void RequestStop()
    {
        if(TrapNodifyList.Count != 0)
        {
            socket.Dispose();                             // 释放线程;
            WaitTrapRunstate = false;                     // 释放监听套接字;
            return;
        }
        else
        {
            return;
        }
    }
}

在其他对象中注册通知消息:

class ProceedWhenGetMessage
{
    ProceedWhenGetMessage()
    {
        TrapMessage.SetNodify(this.proceed);
    }
    
    private void proceed(string Message)
    {
        // Do something
    }
}

同时BCL还贴心提供的几个公用委托:

为了不让程序员再费劲单独自定义委托,造成不必要的混淆,微软在.NET环境下提供了多种不同类型的公用委托,首先总结如下:

委托名称 说明 场景举例
Func<……> 自由功能,包含一个或多个入参,返回一个制定类型的委托 Func:入参为string类型,返回了一个int类型结果
Predicate 是否符合条件,包含一个入参,返回一个bool类型 一般用于判断入参是否符合某个条件,例如List.FindAll(Predicate)
Action 执行动作,包含一个入参,没有返回值,即直接利用入参执行某项操作 一般用于不需要返回结果直接执行动作场景,例如List.ForEach(Action)

Func<……>委托

说明: 表示一种方法,这个方法具有一个或多个参数,并且这个参数返回由TResult指定的数据类型,在.NET3.5之后中,有多个预设的泛型Func委托类型,入参个数最大的可以达到16个。Func系列委托的声明如下:

public delegate TResult Func<in T, out TResult>(T arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

Func 等价于 delegate int SomeDelegate(string arg1, double arg2)

也就是说,如果能获取到一个int类型参数,并想要获取string类型的返回结果,直接使用Func即可。

Predicate 委托

原型:

public delegate bool Predicate<in T>(T obj)

说明: 表示一种方法,这种方法定义一种条件,并返回是否满足了这个条件

举一个例子:

有的时候,经常需要判断一个List中是否存在某些元素,就可以用到FindAll方法,而FindAll方法接受的就是一个Predicate委托:

List<string> strlist = new List<string>();
strlist.Add("1");

List<string> findret = strlist.FindAll(new Predicate<string>(delegate (string instring) {
    if (instring == "1")
        return true;
    else
        return false;
}));

其中FindAll的函数原型如下:

// 摘要:
//     检索与指定谓词定义的条件匹配的所有元素。
//
// 参数:
//   match:
//     System.Predicate`1 委托,用于定义要搜索的元素应满足的条件。
//
// 返回结果:
//     如果找到一个 System.Collections.Generic.List`1,其中所有元素均与指定谓词定义的条件匹配,则为该数组;否则为一个空 System.Collections.Generic.List`1。
//
// 异常:
//   T:System.ArgumentNullException:
//     match 为 null。
public List<T> FindAll(Predicate<T> match);

Action委托

原型:

public delegate void Action();

说明: 表示一种仅需要需要执行系列动作的回调函数,没有返回值

举一个例子:

在C#开发中,有非常多的场景会用到Action,比如在WPF中,想要用另外一个线程往主线程的控件中异步写入内容,就需要用到一个Action委托来处理:

// 注:TrapText是一个Text的WPF控件,并应用了匿名函数
this.TrapText.Dispatcher.Invoke(
   new Action(
        delegate
        {
            foreach (string content in TrapContent)
            {
                TrapText.Text += content + "\r\n";
            }
        }
   )
);

其中Invoke的原型是这样的:

// 摘要:
//     执行指定 System.Action 线程上同步 System.Windows.Threading.Dispatcher 与相关联。
//
// 参数:
//   callback:
//     要通过调度程序调用的委托。
public void Invoke(Action callback);

你可能感兴趣的:(C#基础知识)