• C#进阶之路(四):拉姆达


     对于拉姆达,许多文章都讲过原理及如何使用,所以这篇文章我主要是摘录我学习过的文字,总结下我自己的学习心得。

    什么是拉姆达表达式

      "Lambda表达式"是一个匿名函数,是一种高效的类似于函数式编程的表达式,Lambda简化了开发中需要编写的代码量。它可以包含表达式和语句,并且可用于创建委托或表达式目录树类型,支持带有可绑定到委托或表达式树的输入参数的内联表达式。所有Lambda表达式都使用Lambda运算符=>,该运算符读作"goes to"。Lambda运算符的左边是输入参数(如果有),右边是表达式或语句块。

           Lambda表达式Lambda表达式是由.NET 2.0演化而来的,也是LINQ的基础,熟练地掌握Lambda表达式能够快速地上手LINQ应用开发。

    Lambda表达式在一定程度上就是匿名方法的另一种表现形式

    //匿名方法 
    
    IEnumerable<People> results = people.Where
    
    (delegate(People p) { return p.age > 20; });
    
    //拉姆达表达式
    
    IEnumerable<People> results = people.Where(People => People.age > 20);

    使用示例

    (x, y) => x * y         //多参数,隐式类型=> 表达式 
    
    x => x * 5              //单参数, 隐式类型=>表达式 
    
    x => { return x * 5; }      //单参数,隐式类型=>语句块 
    
    (int x) => x * 5            //单参数,显式类型=>表达式 
    
    (int x) => { return x * 5; }      //单参数,显式类型=>语句块 
    
    () => Console.WriteLine()   //无参数

    以上述格式都是Lambda表达式的合法格式,在编写Lambda表达式时,可以忽略参数的类型,因为编译器能够根据上下文直接推断参数的类型。

    Lambda的进阶用法

    一、闭包

    var a = 5;
    Func<int,int> multiplyWith = x => x * a;
    var result1 = multiplyWith(10); //50
    a = 10;
    var result2 = multiplyWith(10); //100

    我们可以在Lambda表达式中用到外面的变量,没错,也就是传说中的闭包啦。

    void DoSomeStuff()
    {
        var coeff = 10;
        Func<int,int> compute = x => coeff * x;
        Action modifier = () =>
        {
            coeff = 5;
        };
        var result1 = DoMoreStuff(compute);
        ModifyStuff(modifier);
        var result2 = DoMoreStuff(compute);
    }
    int DoMoreStuff(Func<int,int> computer)
    {
        return computer(5);
    }
    void ModifyStuff(Action modifier)
    {
        modifier();
    }
    View Code

      在上面的代码中,DoSomeStuff方法里面的变量coeff实际是由外部方法ModifyStuff修改的,也就是说ModifyStuff这个方法拥有了访问DoSomeStuff里面一个局部变量的能力。它是如何做到的?我们马上会说的J。当然,这个变量作用域的问题也是在使用闭包时应该注意的地方,稍有不慎就有可能会引发你想不到的后果。看看下面这个你就知道了。

    var buttons = new Button[10];
    for (var i = 0; i < buttons.Length; i++)
    {
        var button = new Button();
        button.Text = (i + 1) + ". Button - Click for Index!";
        button.OnClick += (s, e) => { Messagebox.Show(i.ToString()); };
        buttons[i] = button;
    }

    猜猜你点击这些按钮的结果是什么?是”1, 2, 3…”。但是,其实真正的结果是全部都显示10。为什么?不明觉历了吧?那么如果避免这种情况呢?

    var button = new Button();
    var index = i;
    button.Text = (i + 1) + ". Button - Click for Index!";
    button.OnClick += (s, e) => { Messagebox.Show(index.ToString()); };
    buttons[i] = button;

    其实做法很简单,就是在for的循环里面把当前的i保存下来,那么每一个表达式里面存储的值就不一样了。

      当我们的Lambda表达式里面用到了外部变量的时候,编译器会为这个Lambda生成一个类,在这个类中包含了我们表达式方法。在使用这个Lambda表达式的地方呢,实际上是new了这个类的一个实例进行调用。这样的话,我们表达式里面的外部变量,也就是上面代码中用到的local实际上是以一个全局变量的身份存在于这个实例中的。

    二、表达式Expression

    我们可以用一个Expression将一个Lambda保存起来。并且允许我们在运行时去解释这个Lambda表达式。来看一下下面简单的代码:

    Expression<Func<MyModel, int>> expr = model => model.MyProperty;
    var member = expr.Body as MemberExpression;
    var propertyName = member.Expression.Member.Name;

      这个的确是Expression最简单的用法之一,我们用expr存储了后面的表达式。编译器会为我们生成表达式树,在表达式树中包括了一个元数据像参数的类型,名称还有方法体等等。在LINQ TO SQL中就是通过这种方法将我们设置的条件通过where扩展方法传递给后面的LINQ Provider进行解释的,而LINQ Provider解释的过程实际上就是将表达式树转换成SQL语句的过程。

    用Lambda表达式实现一些在JavaScript中流行的模式

    一、返回方法

    我们在JavaScript中可以直接return一个方法,在.net中虽然不能直接返回方法,但是我们可以返回一个表达式。

    Func<string, string> SayMyName(string language)
    {
        switch(language.ToLower())
        {
            case "fr":
                return name => {
                    return "Je m'appelle " + name + ".";
                };
            case "de":
                return name => {
                    return "Mein Name ist " + name + ".";
                };
            default:
                return name => {
                    return "My name is " + name + ".";
                };
        }
    }
    void Main()
    {
        var lang = "de";
        //Get language - e.g. by current OS settings
        var smn = SayMyName(lang);
        var name = Console.ReadLine();
        var sentence = smn(name);
        Console.WriteLine(sentence);
    }

    是不是有一种策略模式的感觉?这还不够完美,这一堆的switch case看着就心烦,让我们用Dictionary<TKey,TValue>来简化它。来看看来面这货:

    static class Translations
    {
        static readonly Dictionary<string, Func<string, string>> smnFunctions = new Dictionary<string, Func<string, string>>();
        static Translations()
        {
            smnFunctions.Add("fr", name => "Je m'appelle " + name + ".");
            smnFunctions.Add("de", name => "Mein Name ist " + name + ".");
            smnFunctions.Add("en", name => "My name is " + name + ".");
        }
        public static Func<string, string> GetSayMyName(string language)
        {
            //Check if the language is available has been omitted on purpose
            return smnFunctions[language];
        }
    }

    二、自定义型方法

    自定义型方法在JavaScript中比较常见,主要实现思路是这个方法被设置成一个属性。在给这个属性附值,甚至执行过程中我们可以随时更改这个属性的指向,从而达到改变这个方法的目地。

    class SomeClass
    {
        public Func<int> NextPrime
        {
            get;
            private set;
        }
        int prime;
    public SomeClass()
        {
           NextPrime = () =>
            {
                prime = 2;
                NextPrime = () => {
                    // 这里可以加上 第二次和第二次以后执行NextPrive()的逻辑代码
                    return prime;
                };
                return prime;
            };
        }
    }

    这里执行可以用

    SomeClass a = new SomeClass();

    Console.WriteLine(a.NextPrime());

    上面的代码中当NextPrime第一次被调用的时候是2,与此同时,我们更改了NextPrime,我们可以把它指向另外的方法,和JavaScrtip的灵活性比起来也不差吧?如果你还不满意 ,那下面的代码应该能满足你。

    Action<int> loopBody = i => {
        if(i == 1000)
            loopBody = //把loopBody指向别的方法
        /* 前10000次执行下面的代码 */
    };
    for(int j = 0; j < 10000000; j++)
        loopBody(j);

    在调用的地方我们不用考虑太多,然后这个方法本身就具有调优性了。我们原来的做法可能是在判断i==1000之后直接写上相应的代码,那么和现在的把该方法指向另外一个方法有什么区别呢?

    三、自执行方法

    JavaScript 中的自执行方法有以下几个优势:

    1  不会污染全局环境

    2  保证自执行里面的方法只会被执行一次

    3  解释完立即执行

    在C#中我们也可以有自执行的方法:

    (() => {

        // Do Something here!

    })();

    上面的是没有参数的,如果你想要加入参数,也非常的简单:

    ((string s, int no) => {
        // Do Something here!
    })("Example", 8);
    .NET4.5最闪的新功能是什么?async?这里也可以
    await (async (string s, int no) => {
        // 用Task异步执行这里的代码
    })("Example", 8);
    // 异步Task执行完之后的代码

    四、对象即时初始化

    大家知道.NET为我们提供了匿名对象,这使用我们可以像在JavaScript里面一样随意的创建我们想要对象。但是别忘了,JavaScript里面可以不仅可以放入数据,还可以放入方法,.NET可以么?要相信,Microsoft不会让我们失望的。

    //Create anonymous object
    var person = new {
        Name = "Jesse",
        Age = 28,
        Ask = (string question) => {
            Console.WriteLine("The answer to `" + question + "` is certainly 42!");
        }
    };
    //Execute function
    person.Ask("Why are you doing this?");

    但是如果你真的是运行这段代码,是会抛出异常的。问题就在这里,Lambda表达式是不允许赋值给匿名对象的。但是委托可以,所以在这里我们只需要告诉编译器,我是一个什么类型的委托即可。

    var person = new {
        Name = "Florian",
        Age = 28,
        Ask = (Action<string>)((string question) => {
            Console.WriteLine("The answer to `" + question + "` is certainly 42!");
        })
    };

    这时候再调用person.Ask("123");

    但是这里还有一个问题,如果我想在Ask方法里面去访问person的某一个属性,可以么?

    var person = new
    {
    Name = "Jesse",
    Age = 18,
    Ask = ((Action<string>)((string question) => {
        Console.WriteLine("The answer to '" + question + "' is certainly 20. My age is " + person.Age );
    }))
    };

    结果是连编译都通不过,因为person在我们的Lambda表达式这里还是没有定义的,当然不允许使用了,但是在JavaScript里面是没有问题的,怎么办呢?.NET能行么?当然行,既然它要提前定义,我们就提前定义好了。

    dynamic person = null;
    person = new {
        Name = "Jesse",
        Age = 28,
        Ask = (Action<string>)((string question) => {
            Console.WriteLine("The answer to `" + question + "` is certainly 42! My age is " + person.Age + ".");
        })
    };
    //Execute function
    person.Ask("Why are you doing this?");

     

     

  • 相关阅读:
    Tomcat 调优的技巧
    双亲委派模型
    字典树实现
    Python获取房价信息和导出EXCEL
    日志检索关键字并截取上下行关联内容
    GC日志分析
    Linux 查看 删除进程
    Rotate partitions in DB2 on z
    C++ STL string
    DB2 for z: system catalog tables
  • 原文地址:https://www.cnblogs.com/qixinbo/p/8520314.html
Copyright © 2020-2023  润新知