• 从扩展方法到匿名方法再到LINQ


    1.首先我们应该知道什么是扩展方法:

    扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。
    扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。
    2.知道了扩展方法怎么创建呢?
    1. 定义一个静态类以包含扩展方法。 
    2. 将该扩展方法实现为静态方法,并使其至少具有与包含类相同的可见性。
    3. 该方法的第一个参数指定方法所操作的类型;该参数必须以 this 修饰符开头。
    4. 在调用代码中,添加一条 using 指令以指定包含扩展方法类的 命名空间。
    5. 按照与调用类型上的实例方法一样的方式调用扩展方法。

     好了,概念性的问题我们都知道了,如果你还没有理解,我们直接写个简单的实例:

    复制代码
    using System;
    
    namespace 扩展方法
    {
        using StringUnit;//添加包含扩展方法类的命名空间
        class Program
        {
            static void Main(string[] args)
            {
               string s1 = "你好";
               string s2 = "rohelm!";
               string s3 = "["+s1.StringCombine(s2).StringCombine("]");
               Console.WriteLine(s3);
               string s4 = StringPro.StringQuote(s2);//也可以利用静态方法调用哦
               string s5 = s1.StringQuote();
               Console.WriteLine(s4.StringCombine(s5));
               Console.ReadKey();
            }
        }
    }
    namespace StringUnit
    {
        static class StringPro//定义一个静态类以包含扩展方法
        {
            public static string StringCombine(this string str1, string str2)//第一个参数必须以 this修饰符开头。 
            {
                return str1 + str2;
            }
            public static string StringQuote(this string str)
            {
                return "[" + str + "]";
            }
        }
    }
    复制代码

     我们从上面的代码中随意的取出一个进行大概的说明:public static string StringQuote(this string str),第一个参数不是由调用代码指定的,因为它表示正应用运算符的类型,并且编译器已经知道对象的类型。也就是这个扩展方法只适用于string类型的运算,因此当我们在Main方法中
    s2.StringQuote 当我们一点的时候StringQuote就会自动奔出来,好像是String类内置的一个方法一样,我们于是可以按照实例方法一样的方式调用扩展方法,当 然我们依然可以使用静态方法一样的方式调用扩展方法例如:StringPro.StringQuote(s2)。这里我们要注意扩展方法和类的static以及this关键字.

    好了我们现在写一个大家最简单的匿名方法,知道我要干什么吗?先写出来。。。

    复制代码
    class Program
        {
            static void Main(string[] args)
            {
             StringDelegate f=delegate(string s1,string s2){
             return s1+s2;
             };
             Console.WriteLine(f("你好","rohelm.X"));
               Console.ReadKey();
            }
        }
        delegate string StringDelegate(string s1,string s2);
    复制代码

    简单的都不好意思解释,好了现在我们简化匿名函数

     StringDelegate f=delegate(string s1,string s2){
             return s1+s2;
    };

    于是乎这句变得非常怪异而简单:

    StringDelegate f=(s1,s2)=>{
             return s1+s2;
             };

    这就是使用Linq和lambda表达式得出的结果,为什么我们不在需要指出s1,s2的参数类型呢,你觉得编译器知道他们该是什么类型吗?

    当然,这就是因为我们这个匿名函数是由delegate string StringDelegate(string s1,string s2); 这个委托约束的,因此当然不用猜测就知道

    s1,s2的数据类型了,这叫类型推断,所以省略了似乎多余的参数类型声明,但是或许对于新手损失可读性,但是他的确非常强大。其实这个例子很简单,就是参数类表的优化而已,后面{}内仍旧是方法体。

    话到这里我们开始新手的Linq之旅

    LINQ是.NET3.5引入的功能,LINQ 通过提供一种跨各种数据源和数据格式使用数据的一致模型,简化了这一情况。

    目的:以统一的方式对数据进行操作。

    Linq和扩展方法又有什么联系?好了我们看一下一个简单的示例:

    int[] numbers = new int[7] { 60, 17, 12, 63, 35, 5, 6 };
     2         IEnumerable<int> query = numbers.OrderByDescending(i=>i);//按照降序排列
     3         foreach (var i in query)
     4         {
     5             Console.Write("{0,3}",i);
     6         }
     7         Console.WriteLine();
     8         IEnumerable<int> query1 = numbers.OrderBy(i => i);//按照升序排列
     9         foreach (var i in query1)
    10         {
    11             Console.Write("{0,3}", i);
     
    12         }

    同样我们在numbers后点的时候会出现

    就好像是Array内置的的方法一样。

    现在我们来看他的函数定义的特点:

    public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector,
        IComparer<TKey> comparer
    )

    由此我们就可以断定Linq内的函数都其实是扩展方法。

    所有 LINQ 查询操作都由以下三个不同的操作组成:
    1. 获取数据源。 
    2. 创建查询。 
    执行查询。
    下面先看看MSDN的官方实例:
    复制代码
    class IntroToLINQ
    {        
        static void Main()
        {
            // The Three Parts of a LINQ Query:
            //  1. Data source.
            int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
    
            // 2. Query creation.
            // numQuery is an IEnumerable<int>
            var numQuery =
                from num in numbers
                where (num % 2) == 0
                select num;
    
            // 3. Query execution.
            foreach (int num in numQuery)
            {
                Console.Write("{0,1} ", num);
            }
        }
    }
    复制代码

     当然没学LinQ我们依然可以老掉牙的这样写:

    复制代码
            int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
            List<int> list = new List<int>();
            foreach (int num in numbers)
            {
                if (num % 2 == 0)
                {
                    list.Add(num);
                }
            }
            foreach (int i in list)
            {
                 Console.Write("{0,1} ", i);
    
    }
    复制代码

     现在我们这样,利用lambda表达式简化上面的官方示例,只需改一句即可完成相同的功能:

     int[] numbers = new int[7] { 60, 17, 12, 63, 35, 5, 6 };
            IEnumerable<int> query = numbers.Where(i=>i%2==0);

    同时,官方示例内部实行时其实编译器依旧将其转化为lambda表达式的形式。

    linq 的查询语句看起来非常像SQL语句,但是和SQL无关。

    现在来简要的介绍一下常用的语句,和同学们一起学习下,我也是刚刚学的,不过了解这些还是皮毛,要深入的要就就要熟练掌握必须花时间去深入研究。

    1.查询表达式必须以 from子句开头

    1. from 子句中引用的数据源的类型必须为 IEnumerable、 IEnumerable < T >或一种派生类型(如 IQueryable <T >)。
    2. 查询表达式还可以包含子查询,子查询也是以 from 子句开头。
    2.where 子句用在查询表达式中,用于指定将在查询表达式中返回数据源中的哪些元素。
    3. select 子句可以指定将在执行查询时产生的值的类型。该子句的结果将基于前面所有子句的计算结果以及 select 子句本身中的所有表达式。查询表达式必须以 select 子句或 group 子句结束。
    4.group 子句返回一个 IGrouping <TKey, TElement >对象序列,这些对象包含零个或更多个与该组的键值匹配的项。
    5.by 上下文关键字用在查询表达式的 group 子句中,用于指定应该如何对返回的项进行分组。
    6.在查询表达式中,存储子表达式的结果有时很有用,这样可以在随后的子句中使用。可以使用 let 关键字完成这一工作,该关键字可以创建一个新的范围变量,并且用您提供的表达式的结果初始化该变量。一旦用值初始化了该范围变量,它就不能用于存储其他值。但如果该范围变量存储的是可查询的类型,则可以对其进行查询。
    7.在查询表达式中, orderby 子句可使返回的序列或子序列(组)按升序或降序排序。
    8.使用 join 子句可以将来自不同源序列并且在对象模型中没有直接关系的元素相关联。
    9.ascending 上下文关键字用在查询表达式的 orderby 子句中,用于指定从最小到最大的排序顺序。
    10.descending 上下文关键字用在查询表达式的 orderby 子句中,用于指定从最大到最小的排序顺序。
    11.equals 上下文关键字用在查询表达式的 join 子句中,用于比较两个序列的元素。
    12.可以使用 into 上下文关键字创建一个临时标识符,以便将 group、 join 或 select 子句的结果存储到新的标识符中。
    到此我们先熟悉一下这几个查询关键字,做个小测试:
    复制代码
     static void Main()
        {
            string values = "abcdefasfsafcdekjekyursdjdagghiaaefrxfgs";
            var query =
                     from value in values
                     group value by value into repeatGroup//通过使用into标识符,可以对每个组调用 Count 方法
                     orderby repeatGroup.Count() descending//降序排列
                     select new { 字符 = repeatGroup.Key, 次数 = repeatGroup.Count() };
            //通过select返回匿名类型对象实现返回多列的效果
            foreach (var cha in query)
            {
                Console.WriteLine(cha.字符 + "=" + cha.次数);
            }
            Console.ReadKey();
        }
    复制代码

    刚好发现一个嵌套的form子句的例子,里面使用let子句来保存临时变量,也就是句子拆成单词再返回合适条件的。

    1 using System;
     2 using System.Linq;
     3 class LetSample1
     4 {
     5     static void Main()
     6     {
     7         string[] strings =
     8         {
     9             "A penny saved is a penny earned.",
    10             "The early bird catches the worm.",
    11             "The pen is mightier than the sword."
    12         };
    13
    14         var earlyBirdQuery =
    15             from sentence in strings
    16             let words = sentence.Split(' ')
    17             from word in words
    18             let w = word.ToLower() 
    19             where w[0] == 'a' || w[0] == 'e'
    20                 || w[0] == 'i' || w[0] == 'o'
    21                 || w[0] == 'u'
    22             select word;
    23
    24         // Execute the query.
    25         foreach (var v in earlyBirdQuery)
    26         {
    27             Console.WriteLine(""{0}" starts with a vowel", v);
    28         }
    29
    30         // Keep the console window open in debug mode.
    31         Console.WriteLine("Press any key to exit.");
    32         Console.ReadKey();
    33     }
    34 }

    下面的方法都是IEnumerable<T>的扩展方法:
    Average计算平均值;

    Min最小元素;

    Max最大元素;

    Sum元素总和;

    Count元素数量;
    Concat连接两个序列;
    Contains序列是否包含指定元素;
    Distinct取得序列中的非重复元素;
    Except获得两个序列的差集;
    Intersect获得两个序列的交集;
    First取得序列第一个元素;
    Single取得序列的唯一一个元素,如果元素个数不是1个,则报错

    FirstOrDefault 取得序列第一个元素,如果没有一个元素,则返回默认值;
    Linq只能用于范型的序列,IEnumerable<T>,对于非范型,可以用Cast或者OfType
    IEnumerable的方法:
    Cast<TResult>:由于Linq要针对范型类型操作,对于老版本.Net类等非范型的IEnumerable序列可以用Cast方法转换为范型的序列。ArrayList l; IEnumerable<int> il = l.Cast<int>();
    OfType<TResult>:Cast会尝试将序列中所有元素都转换为TResult类型,如果待转换的非范型序列中含有其他类型,则会报错。OfType则是只将序列中挑出指定类型的元素转换到范型序列中。
    Linq对于小数据量、对性能要求不高的环节用linq很方便,而且延迟加载机制降低了内存占用,比一般人写的程序效率都高。 

  • 相关阅读:
    [BZOJ 4117] Weather Report
    [BZOJ 3233] 找硬币
    集合迭代器Iterator
    如何实现数组与List的相互转换?在 Queue 中 poll()和 remove()有什么区别?哪些集合类是线程安全的?
    ArrayList分别与LinkedList、Vector、Array的区别
    HashMap与TreeMap
    HashSet原理
    并发场景下HashMap死循环导致CPU100%的问题
    HashMap工作原理
    HashMap与HashTable的区别
  • 原文地址:https://www.cnblogs.com/wdcwy/p/5168574.html
Copyright © 2020-2023  润新知