• CSharpThinking委托相关(二)


    学而不思则罔,思而不学则殆。

      本章主要内容是介绍委托的发展历程及不同阶段的优缺点。文章最后给出了一些概念的解释。

       

    关于委托:

      C#1:用特定的签名实现委托,委托相当于方法的“指针”或者叫方法的引用。

        不足:1.如果想实现一个小委托也要创建一个完整的新方法。

                2.方法名过长。

    #region C#1
    Button btn = new Button();
    btn.Click += new EventHandler(tb_Click);
    
    ....
    
    void tb_Click(object sender, EventArgs e)
    {
          // throw new NotImplementedException();
    }
    #endregion

      C#2:提供了匿名方法,泛型委托类型Action<T>. 以及带返回值的Predicate<T>(T obj)

         优点:简化委托delegate(first,second){ return first < second}; 如果不需要参数值,可使用匿名委托 delegate{...} 。

                Button tb = new Button();
                tb.Click += delegate(object sender, EventArgs e)
                {
                    Console.WriteLine(string.Format("非匿名委托,命令源{0}.", sender));
                };
                tb.Click += delegate
                {
                    Console.WriteLine("匿名委托,无需参数。");
                };

         注:匿名方法是C#2以被捕捉的变量的形式来实现,在别的地方称为闭包的一个特性。 ---C#InDepth

      

      C#3:提供Lambda,表达式树和Linq

       1.Lambda表达式:(显式参数列表)=>{...}  

        1.1.Lambda表达式发展历程

    历程 表达式 备注
    1.匿名方法 delegate(string Text){return Text.Length;} 匿名方法
    2.转换为Lambda表达式 (string Text) => {return Text.Length;} 转换
    3.单个表达式 (string Text) => Text.Length; 去除不必要的大括号
    4.让编译器推断隐式参数类型 (Text) => Text.Length; 编译器能猜测的出来
    5.去除不必要的括号 Text => Text.Length; 最简形式,但有限制

        1.2.Lambda表达式针对List的操作: 编译器执行自动转换及缓存Lambda表达式

     1     public class Item { public int Year { get; set; } }
     2 
     3     public class Lambda表达式
     4     {
     5         public Lambda表达式() 
     6         {
     7             List<Item> Items = new List<Item>(); 
     8 
     9             Items.FindAll(item => item.Year < 1960); // Lambda表达式
    10 
    11        Items.FindAll(new Predicate<Item> (SomeAutoGenerateName)); // 编译器转化后的Lambda表达式,编译器会自动缓存Lambda表达式
    12         }
    13 
    14         private static bool SomeAutoGenerateName(Item item)
    15         {
    16             return item.Year < 1960;
    17         }
    19     }

        1.3. 延迟加载

                    恰当的时间真正做转换,使数据能在适当的位置以一种“Just-In-Time”方式提供.

    1 var col = Enumerable.Range(0,10).Reverse();// 实际并未执行任何操作,执行时间为O(1);
    2 
    3 foreach(var element in col)
    4 {
    5       Console.WriteLine(element); // 此时调用才会真正执行....Reverse(), 前期只是准备。即,延迟执行。   
    6 }

      

      2.表达式树 :代码作为数据,树形表达式集合

       2.1.表达式树的工作方式:我们在程序中创建了一些逻辑块,将其表示成普通对象,然后要求框架将所有的东西都编译成可执行的“真实”的代码。可能永远也不会这样用,但有助于理解Linq的工作方式。

     1         /// <summary>
     2         /// 简洁版表达式树
     3         /// </summary>
     4         void SimpleExpressionTree()
     5         {
     6             Expression<Func<String, String, bool>> exp = (x, y) => x.StartsWith(y);
     7             var compiled = exp.Compile();
     8             Console.WriteLine(compiled("A", "B"));
     9         }
    10 
    11         /// <summary>
    12         /// 简洁版表达式树用lambdaExpression转换的实例
    13         /// </summary>
    14         /// <remarks>
    15         /// 目的:了解表达式树底层实现方式,便于了解工作流程。
    16         /// </remarks>
    17         void ComplexExpressionTree()
    18         {
    19             // 1.构造方法调用各个部件
    20             MethodInfo method = typeof(string).GetMethod("StartWith", new[] { typeof(string) });
    21             var target = Expression.Parameter(typeof(string), "x");
    22             var methodArg = Expression.Parameter(typeof(string), "y");
    23             Expression[] methodArgs = new[] { methodArg };
    24             // 2.创建调用表达式
    25             Expression call = Expression.Call(target, method, methodArgs);
    26             var lambdaParameters = new[] { target, methodArg };
    27             var lambda = Expression.Lambda<Func<string, string, bool>>(call, lambdaParameters);
    28             // 3.将Call转换成Lambda表达式
    29             var compiled = lambda.Compile();
    30             Console.WriteLine(compiled("A", "B"));
    31         }

        2.2.表达式树常用在Linq,但不总是这样。 Linq = 表达式树+Lambda+扩展方法

    Bjarne Stroustrup :“我不会创建这样的工具,它所能做的,都是我可以想象得到的。”

        2.3.表达式树是构建动态语言的核心。

     

     

     重要概念解释:

    一. 闭包:一个函数除了能通过提供给它的参数与环境的互动外,还能同环境进行更大程度的互动。 

    例子:

     1          public delegate void MethodInvoker();
     2         void EnClosingMethod()
     3         {
     4             int outerVariable = 5; // 外部变量,未捕获。匿名方法没有用它,所以未捕获。
     5             string capturedVariable = "captured"; // 被匿名方法捕获的外部变量。
     6 
     7             if (DateTime.Now.Hour == 23)
     8             {
     9                 int normalLocalVariable = DateTime.Now.Minute; // 普通方法的局部变量,作用域内没有匿名方法。
    10                 Console.WriteLine(normalLocalVariable);
    11             }
    12 
    13             MethodInvoker x = delegate()
    14             {
    15                 string anonLocal = "Local to anonymous method"; // 匿名方法的局部变量,只有在匿名方法被调用时才存在于正在执行的栈帧(Frame)中。
    16                 Console.WriteLine(capturedVariable + anonLocal); // 捕获外部变量
    17             };
    18 
    19             x();
    20         }

    被捕捉的变量的生存期延长,局部变量并不总是“局部”。

     1         static MethodInvoker CreateDelegateInstance()
     2         {
     3             int counter = 5; // 不要认为counter的作用域仅仅在CreateDelegateInstance中。
     4             MethodInvoker x = delegate()
     5             {
     6                 Console.WriteLine(counter);
     7                 counter++;
     8             };
     9             x();
    10             return x;
    11         }
    12 
    13         void Test() 
    14         {
    15             MethodInvoker invoker = CreateDelegateInstance();// 整个CreateDelegateInstance在堆上而非栈上,故能保存counter值。
    16             invoker();
    17             invoker();
    18 
    19             /* Output:
    20              * 5
    21              * 6
    22              * 7
    23              * */
    24         }

     使用多个委托来捕获多个变量的实例:

     1         public static void MutiDelegate()
     2         {
     3             List<MethodInvoker> list = new List<MethodInvoker>();
     4 
     5             for (int index = 0; index < 5; index++)
     6             {
     7                 int counter = index * 10;
     8                 list.Add(delegate()
     9                 {
    10                     Console.WriteLine(counter);// 若改成index ,则输出全部为5
    11                     counter++;
    12                 });
    13             }
    14              
    15             foreach (var item in list)
    16             {
    17                 item(); // 执行委托实例
    18             }
    19 
    20             list[0]();// Output: 1
    21             list[0]();// Output: 2
    22             list[1]();// Output: 11
    23 
    24             /*
    25              * counter在循环内部声明,所以会生成5个counter副本。
    26              * 而index仅仅声明了一次,所以有唯一一个副本。
    27              * */
    28         }

     捕获变量小结:

    1.捕获的是变量,而不是创建委托实例时它的值。

    2.捕获的变量的生存期被延长了,至少和捕捉它的委托一样长。

    3.多个委托可以捕获同一个变量。

    4.必要时创建额外的类型来保存捕获变量。

    5.如果用或不用捕获变量同样简单及简洁,那就不用。

    6.考虑垃圾回收问题,尽量不要延长捕获变量的生存期。

      

    二. C#3 编译器推断Lambda表达式中的参数类型,相比C#2要复杂的多。

      

     

  • 相关阅读:
    【C#】3.算法温故而知新
    【C#】2.算法温故而知新
    【C#】1.算法温故而知新
    【C#】SQL数据库助手类2.0(自用)
    【Javascript Demo】JS获取当前对象大小以及屏幕分辨率等
    【Android 基础】Android中全屏或者取消标题栏
    【ASP.NET 问题】System.InvalidOperationException: 对象的当前状态使该操作无效 【大量表单数据提交】错误解决
    【CSS】颜色码对照表
    【Ext.Net学习笔记】07:后续
    【Ext.Net学习笔记】06:Ext.Net GridPanel的用法(GridPanel 折叠/展开行、GridPanel Selection、 可编辑的GridPanel)
  • 原文地址:https://www.cnblogs.com/cuiyansong/p/3103267.html
Copyright © 2020-2023  润新知