C# 委托、匿名方法、Lambda表达式

83 阅读4分钟

概念

委托(delegate)是函数指针的升级版

直接调用:通过函数名获得函数地址调用函数(执行地址中的指令) 间接调用:通过读取函数指针中存储的函数地址调用函数

Java中为提高应用安全性,禁止程序员直接访问内存地址,故没有委托的概念。

Action 和 Func 是 C# 内置的委托,它们都有很多重载以方便使用。

class Program
{
    static void Main(string[] args)
    {
        var calculator = new Calculator();
        // Action 用于无形参无返回值的方法。
        Action action = new Action(calculator.Report);
        calculator.Report();
        action.Invoke();
        // 模仿函数指针的简略写法。
        action();

        Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);//此处用到泛型
        Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);

        int x = 100;
        int y = 200;
        int z = 0;

        z = func1.Invoke(x, y);
        Console.WriteLine(z);
        z = func2.Invoke(x, y);
        Console.WriteLine(z);

        // Func 也有简略写法。
        z = func1(x, y);
        Console.WriteLine(z);
        z = func2(x, y);
        Console.WriteLine(z);
    }
}

class Calculator
{
    public void Report()
    {
        Console.WriteLine("I have 3 methods.");
    }

    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Sub(int a, int b)
    {
        return a - b;
    }
}

委托基础操作

委托的声明 委托是一种类,故声明的位置和类一致。声明方式区别于类(延续C/C++传统和增加可读性)

delegate double Calc(double x,double y);//声明一个委托,该委托接收返回值为double类型、带有两个double型参数的方法

委托类型变量的声明和初始化

Calc calc = new Calc(calculator.Add);//封装一个实例方法
Calc calc = calculator.Add;//简写
calc = new Calc(Sclass.method);//封装一个静态方法的委托实例,赋值给之前的引用

为委托添加方法到方法列表 使用+=运算符

MyDel delVar = inst.MyM1;//声明变量并初始化
delVar += Scl.m3;//向委托封装的方法列表底部添加新的方法
delVar += X.Act;//同上

注意:每次添加方法,会产生新的包含新方法和旧方法的组合的一个新的委托实例,并将新的实例赋值给变量

为委托移除方法 使用-=运算符 同样会创建新的委托实例,包含移除给定方法后的剩余方法,若移除空委托实例会抛出异常 移除方法时会从方法列表底部向上搜寻符合待移除方法的第一个实例。

调用有返回值的方法列表,会返回方法列表的最后一个方法的返回值

委托的应用

模板方法

借用外部方法产生一个结果

委托有返回值 常位于代码中部 相当于“填空题”

class Program
{
    static void Main(string[] args)
    {
        var productFactory = new ProductFactory();

        Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
        Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);

        var wrapFactory = new WrapFactory();
        Box box1 = wrapFactory.WrapProduct(func1);
        Box box2 = wrapFactory.WrapProduct(func2);

        Console.WriteLine(box1.Product.Name);
        Console.WriteLine(box2.Product.Name);
    }
}

class Product
{
    public string Name { get; set; }
}

class Box
{
    public Product Product { get; set; }
}

class WrapFactory//打包产品
{
    // 模板方法,提高复用性
    public Box WrapProduct(Func<Product> getProduct)
    {
        var box = new Box();
        Product product = getProduct.Invoke();
        box.Product = product;
        return box;
    }
}

class ProductFactory
{
    public Product MakePizza()//生产pizza
    {
        var product = new Product();
        product.Name = "Pizza";
        return product;
    }

    public Product MakeToyCar()//生产玩具车
    {
        var product = new Product();
        product.Name = "Toy Car";
        return product;
    }
}

假设我们现在需要生产新的产品,就要在ProductFactory中添加新的Make...方法。

回调(callback)方法

调用指定的外部方法

委托无返回值 常位于代码尾部 相当于“流水线”

class Program
{
    static void Main(string[] args)
    {
        var productFactory = new ProductFactory();
        
        // Func 前面是传入参数,最后一个是返回值,所以此处以 Product 为返回值
        Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
        Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);

        var wrapFactory = new WrapFactory();
        var logger = new Logger();
        // Action 只有传入参数,所以此处以 Product 为参数
        Action<Product> log = new Action<Product>(logger.Log);

        Box box1 = wrapFactory.WrapProduct(func1, log);
        Box box2 = wrapFactory.WrapProduct(func2, log);

        Console.WriteLine(box1.Product.Name);
        Console.WriteLine(box2.Product.Name);
    }
}

class Logger
{
    public void Log(Product product)
    {
        // Now 是带时区的时间,存储到数据库应该用不带时区的时间 UtcNow。
        Console.WriteLine("Product '{0}' created at {1}.Price is {2}", product.Name, DateTime.UtcNow, product.Price);
    }
}

class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
}

class Box
{
    public Product Product { get; set; }
}

class WrapFactory
{
    // 模板方法,提高复用性
    public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallBack)
    {
        var box = new Box();
        Product product = getProduct.Invoke();

        // 只 log 价格高于 50 的
        if (product.Price >= 50)
        {
            logCallBack(product);
        }

        box.Product = product;
        return box;
    }
}

class ProductFactory
{
    public Product MakePizza()
    {
        var product = new Product
        {
            Name = "Pizza",
            Price = 12
        };
        return product;
    }

    public Product MakeToyCar()
    {
        var product = new Product
        {
            Name = "Toy Car",
            Price = 100
        };
        return product;
    }
}

委托和匿名方法

有些方法只需被使用一次,这些方法不是类的成员。匿名方法是在实例化委托时内联(inline)声明的方法。

Class Program
{
    delegate int OtherDel(int InParam);
    static void main()
    {
        OtherDel del = delegate(int x){return x+20;};//输入int x 返回int型
        Console.WriteLine("0",del(5));//输出25
    }
}

匿名方法没有显式声明返回值,但其返回值必须与委托的返回类型相同。

匿名方法的参数和方法中的局部变量在匿名方法主体外无效

匿名方法可以捕获外部变量,但变量被捕获后在方法体外会失效

Lambda表达式

C#2.0引入匿名方法,C#3.0引入Lambda表达式,简化匿名方法的语法。

Mydel del = delegate (int x)  {return x+1;};//匿名方法
Mydel del =          (int x)=>{return x+1;};//Lambda表达式
Mydel del =         	 (x)=>{return x+1;};//隐式类型 (定义委托的时候有类型)
Mydel del =               x=>{return x+1;};//如果参数唯一,进一步简化
Mydel del =               x=>x+1;表达式主体只保留return后面的表达式