zoukankan      html  css  js  c++  java
  • [.net 面向对象编程基础] (19) LINQ基础

    [.net 面向对象编程基础] (19)  LINQ基础 

       上两节我们介绍了.net的数组、集合和泛型。我们说到,数组是从以前编程语言延伸过来的一种引用类型,采用事先定义长度分配存储区域的方式。而集合是.Net 版本初期的用于解决数据集检索方便而设计的,它比数组的优势除了检索方便之外,还可以在使用过程中自动分配存储区域,不需要事先定义大小。但是集合存在类型不安全以及频繁装箱、拆箱操作带来的性能问题。泛型是.net 2.0以后为了解决集合的缺陷而设计的,采用实例调用阶段再声明类型的方法,即解决了安全问题,也解决了效率问题。 

    随着.net 3.5以后版本的发展,微软设计了LINQ,让我们检索更加方便,开发更加高效了。 

    1.LINQ概念 

    LINQ,语言集成查询(Language Integrated Query是一组用于c#Visual Basic语言的扩展。它允许编写C#或者Visual Basic代码以查询数据库相同的方式操作内存数据。 

    以上是来自百度百科的定义。 

    通过英文名描述,我们可以看出,它就是一种让开发者像查询数据库一样,去检索内存数据的方案。 

    2.LINQ学习之前需要掌握以下知识点

    看着有点多,但是都是学好LINQ的基础,我们说了LINQ就是像SQL语句一样操作内存数据,那么学习SQL要掌握表,字段,视图的基础概念,LINQ也一样。 

    2.1 隐式类型 

    之前我们定义变量时,需要指定一个类型,foreach遍历时,也要指定类型。隐式类型的出现我们不再需要做这个工作了,而使用var 定义就可以了,这点更像弱类型语言比如javascipt。但是.NET并不是弱类型语言,隐式类型的定义写法如下: 

    var i=1;
    var b=”xyz”;
    var obj=new MyObj();

    以上隐式类型等效于 

    int i=1;
    string b=”xyz”;
    MyObj obj=new MyObj();

    关于隐式类型的几点说明: 

    A.有了隐式类型,可以编码中使用var定义任意类型变量 

    B.隐式类型并不会影响程序性能,.NET会在编译时帮我们转换成具体的数据类型,因此我们必须声明隐式类型时赋值,不然.NET 不能判断具体转成什么类型而报错。

    C.隐式类型var 使我们开发节约了不少时间。定义一个变量时,类型需要输两次,var 一下就搞定了。在foreach遍历的时候也可 以使用var来书写循环变量的类型。 

    2.2 匿名类型 

    我们new一个对象,它里面的元素,类型可以不申明,而使用匿名方式。如下: 

    //匿名类型
    var obj =new  { str = "abc", Guid.Empty,myArray = new int[] { 1, 2, 3 } };
    Console.WriteLine(obj.str);
    Console.WriteLine(obj.Empty);
    Console.WriteLine(obj.myArray[0]);
    Console.ReadLine();

     运行结果如下:

    我们看到new 一个对象后,自动为对象定义了属性,并为这些属性赋值,当把一个对象的属性拷贝到匿名对象中时,可以不用显示的指定属性的名字,这时原始属性的名字会被“拷贝”到匿名对象中.如下图:

     

    自动创建了obj.Empty属性名。
    如果你监视变量obj,你会发现,obj的类型是Anonymous Type类型的。
    不要试图在创建匿名对象的方法外面去访问对象的属性!
    这个特性在网站开发中,序列化和反序列化JSON对象时很有用。

    2.3 自动属性

    记得在“重构”一节中,说了属性的重构,只需要字段名上使用vs.net提供的“封装字段”,就为我们生成了相对应的属性,如下图:

    (图1)


    (图2)

    (图3)

    .net 3.0以后,我们代码可以写的更简单了,代码如下: 

    //自动属性
    class Flower {
        public string Leaf{get;set;}
        public string Name{get;set;}
    }

    重构当然只是.net的辅助功能,对于自动属性小伙伴们肯定会担心这样写的效率,这点完全可以放心,跟var 隐式类型一样,.net在编译的时候,会帮我们写完整的,没有任何性能问题。

    2.4 初始化器

    对于一个对象的初始化,假如有以下两个对象:

    //初始化器
    class Flower {
        public string Leaf{get;set;}
        public string Name{get;set;}
    }
    
    class Bear
    {
        public Bear(string name){}
        public string Name { get; set; }
        public double Weight { get; set; }
    
    }

    3.0以前的版中,我们一般类似于如下写法:

    //初始化器
    Flower flower = new Flower();
    flower.Leaf = "叶子";
    flower.Name = "棉花";

    3.0以后的版本中,我们可以这样写:

    //.net 3.0以后对象初始化可以如下写法
    Flower flower2 = new Flower() { Name = "桃花", Leaf = "桃叶" };
    //构造函数带参数对象初始化
    Bear bear = new Bear("北极熊"){ Name="熊熊", Weight=300};
    //集合初始化
    var array = new List<char>() { 'a', 'b','c', 'd', 'e','f' };

    我们可以看出,在写法上简洁好多了。特别是对泛型集合的赋值,相当简洁明了。

    2.5 匿名方法

    说到匿名方法,就是说在委托一个方法的时候,可以把方法写在委托声明的时候,简单说就是不用把方法单独写出来。在说这个之前,我们本来是要介绍一下委托的,但是下一节,我们会重点说明委托,在这里只需要了解匿名方法就可以了,下面看一个例子: 

     1 // 委托函数
     2 Func<int, int, string> func1 = Adds;
     3 // 匿名方法
     4 Func<int, int, string> func2 =
     5     delegate(int a, int b)
     6     {
     7         return a+"+"+b+"等于几?" + Environment.NewLine +(a+b).ToString();
     8     };
     9 // Lambda表达式
    10 Func<int, int, string> func3 =
    11     (a, b) => { return a + "+" + b + "等于几?" + Environment.NewLine + (a + b).ToString(); };
    12 
    13 // 调用Func委托
    14 string sum = func2(45, 36);
    15 
    16 Console.WriteLine(sum);
    17 Console.ReadLine();
    //委托方法
    static string Adds(int a, int b)
    {
        return a + "+" + b + "等于几?" + Environment.NewLine + (a + b).ToString(); 
    }

    通过使用匿名方法,可以访问上下文中的变量,在方法本身不是很长的情况下,轻量级的写法,非常实用。

    这点在下一节委托中具体说明,小伙伴在这里没看明白也没关系。 

    2.6 扩展方法

    1) 扩展方法声明在静态类中,定义为一个静态方法,其第一个参数需要使用this关键字标识,指示它所扩展的类型。

    2) 扩展方法可以将方法写入最初没有提供该方法的类中。还可以把方法添加到实现某个接口的任何类中,这样多个类就可以使用相同的实现代码。(LINQ中,System.Linq.Queryable.csSystem.Linq.Enumerable.cs 正是对接口添加扩展方法)

    3) 扩展方法虽定义为一个静态方法,但其调用时不必提供定义静态方法的类名,只需引入对应的命名空间,访问方式同实例方法。

    4) 扩展方法不能访问它所扩展的类型的私有成员。

    例子:

    public static IEnumerable<TSource> MyWhere<TSource>(
        this IEnumerable<TSource> source, Func<TSource, bool> predicate)
     {
                foreach (TSource item in source)
                {
                    if (predicate(item))
                        yield return item;
                }
    }

     我们再看一下扩展方法的厉害的地方,要给一个类型增加行为,不一定要使用继承的方法实现,还可以这样写:

    var a = "aaa";
    a.PrintString(); 
    Console.ReadKey();

    但通过我们上面的代码,就给string类型"扩展"了一个PrintString方法。

    1)先决条件

    <1>扩展方法必须在一个非嵌套、非泛型的静态类中定义

    <2>扩展方法必须是一个静态方法

    <3>扩展方法至少要有一个参数

    <4>第一个参数必须附加this关键字作为前缀

    <5>第一个参数不能有其他修饰符(比如ref或者out

    <6>第一个参数不能是指针类型

    2)注意事项

    <1>跟前面提到的几个特性一样,扩展方法只会增加编译器的工作,不会影响性能(用继承的方式为一个类型增加特性反而会影响性能)

     <2>如果原来的类中有一个方法,跟你的扩展方法一样(至少用起来是一样),那么你的扩展方法奖不会被调用,编译器也不会提示你

     <3>扩展方法太强大了,会影响架构、模式、可读性等等等等....

    2.7 迭代器

    1)使用

    我们每次针对集合类型编写foreach代码块,都是在使用迭代器

    这些集合类型都实现了IEnumerable接口

    都有一个GetEnumerator方法

    但对于数组类型就不是这样

    编译器把针对数组类型的foreach代码块

    替换成了for代码块。

    来看看List的类型签名: 

    public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable

    IEnumerable接口,只定义了一个方法就是:   

    IEnumerator<T> GetEnumerator();

    2)迭代器的优点

     假设我们需要遍历一个庞大的集合

     只要集合中的某一个元素满足条件

     就完成了任务

     你认为需要把这个庞大的集合全部加载到内存中来吗?

     当然不用(C#3.0之后就不用了)!

     来看看这段代码:         

    static IEnumerable<int> GetIterator()
    {
        Console.WriteLine("迭代器返回了1");
        yield return 1;
        Console.WriteLine("迭代器返回了2");
        yield return 2;
        Console.WriteLine("迭代器返回了3");
        yield return 3;
    }
    foreach (var i in GetIterator())
    {
        if (i == 2)
        {
            break;
        }
        Console.WriteLine(i);
    }
    Console.ReadKey();

    输出结果为:    

    迭代器返回了1
    
    1
    
    迭代器返回了2

    大家可以看到:

    当迭代器返回2之后,foreach就退出了

    并没有输出迭代器返回了3”

    也就是说下面的工作没有做。

    3yield 关键字

    MSDN中的解释如下:

    在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。

    也就是说,我们可以在生成迭代器的时候,来确定什么时候终结迭代逻辑

    上面的代码可以改成如下形式:  

    static IEnumerable<int> GetIterator()
    {
        Console.WriteLine("迭代器返回了1");
        yield return 1;
        Console.WriteLine("迭代器返回了2");
        yield break;
        Console.WriteLine("迭代器返回了3");
        yield return 3;
    }

    (4)注意事项

    <1>foreach循环时多考虑线程安全性      

    foreach时不要试图对被遍历的集合进行removeadd等操作

    任何集合,即使被标记为线程安全的,在foreach的时候,增加项和移除项的操作都会导致异常

    <2>IEnumerable接口是LINQ特性的核心接口

    只有实现了IEnumerable接口的集合

    才能执行相关的LINQ操作,比如select,where

    关于LINQ的具体操作,下一节继承。

    2.8 Lambda表达式

    Lambda表达式只是用更简单的方式来书写匿名方法,从而彻底简化.NET委托类型的使用。  

    Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。

    通过上面匿名方法的例子,我们可以看到,下面两段代码是等效的:

    // 匿名方法
    Func<int, int, string> func2 =
        delegate(int a, int b)
        {
            return a+"+"+b+"等于几?" + Environment.NewLine +(a+b).ToString();
        };
    // Lambda表达式
    Func<int, int, string> func3 =
        (a, b) => { return a + "+" + b + "等于几?" + Environment.NewLine + (a + b).ToString(); };

    Lambda表达式基于数学中的λ(希腊第11个字母)演算得名,而“Lambda 表达式”(lambda expression)是指用一种简单的方法书写匿名方法。

    3.要点:

    A.LINQ,语言集成查询(Language Integrated Query)是一种语言扩展,让我们像查询数据库一样去操作内存数据(集合等).

    B.匿名类型:使用new声明匿名类型时,不需指明类型,.NET 3.0以后版本,可以自动完成类型指定和指定属性名称

    C.自动属性:.net 3.0 以后版本中,我们定义属性,只需要简单书写get;set;就可以了,.net在编译阶段会帮助我们完成书写,不会存在性能问题。

    D.初始化器3.0以后版本中,可以更加简单的书写对象初始化,特别是对泛型集合的赋值,相当简洁明了。

    E.匿名方法:在委托方法时,无需写明方法名称.使用匿名方法可以访问上下文中的变量,在方法代码较少的情况下,轻量级实现委托,非常实用。

    F.扩展方法:可以在不使用继承的方式给一个类型增加行为

    G.迭代器:在遍历一个庞大集合时,不需要将它全部加载于是内存中,只要满足条件就可以返回了。

    H.Lambda表达式:Lambda表达式基于数学中的λ(希腊第11个字母)演算得名,而“Lambda 表达式”(lambda expression)是指用一种简单的方法书写匿名方法

    备注:本文参考了博友们的一些文章,数目较多,不一一列举,在此表示感谢。

    对于LINQ的使用我们下一节详细说明。 

    ==============================================================================================  

     返回目录

     <如果对你有帮助,记得点一下推荐哦,如有有不明白或错误之处,请多交流>  

    <转载声明:技术需要共享精神,欢迎转载本博客中的文章,但请注明版权及URL>

    .NET 技术交流群:467189533    .NET 程序设计

    ==============================================================================================   

  • 相关阅读:
    使用C#实现DHT磁力搜索的BT种子后端管理程序+数据库设计(开源)
    便携版WinSCP在命令行下同步文件夹
    ffmpeg (ffprobe)分析文件关键帧时间点
    sqlite删除数据或者表后,回收数据库文件大小
    ubuntu 20.04下 freeswitch 配合 fail2ban 防恶意访问
    ffmpeg使用nvenc编码的结论记录
    PC版跑跑卡丁车 故事模式 亚瑟传说章节 卡美洛庆典 2阶段 心灵之眼 攻略
    There was an error loading or playing the video
    Nvidia RTX Voice 启动报错修复方法
    火狐浏览器 关闭跨域限制
  • 原文地址:https://www.cnblogs.com/yubinfeng/p/4570688.html
Copyright © 2011-2022 走看看