(第一篇) 一步一步帶你了解linq to Object


要想學好linq to object 我們必須要先學習lambda 表達式,學習lambda 表達式呢我們必須了解匿名函數和匿名類及擴展方法,學習匿名函數,我們必須學會委托,這是本文的宗旨。下面開始第一步。在第一步開始之前,我們做點准備工作,建立一個學生類和一個班級類,類結構如下

   public class Student
    {
        public int Id { get; set; }
        public int ClassId { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }

        public void Study()
        {
            Console.WriteLine("{0} {1}跟着Eleven老師學習.net高級開發", this.Id, this.Name);
        }
    }

    public class Class
    {
        public int Id { get; set; }
        public string ClassName { get; set; }
    }

在准備一個基礎數據類

 public class LinqShow
    {
        /// <summary>
        /// 准備數據
        /// </summary>
        /// <returns></returns>
        public List<Student> GetStudentList()
        {
            #region 初始化數據
            List<Student> studentList = new List<Student>()
            {
                new Student()
                {
                    Id=1,
                    Name="打兔子的獵人",
                    ClassId=2,
                    Age=35
                },
                new Student()
                {
                    Id=1,
                    Name="Alpha Go",
                    ClassId=2,
                    Age=23
                },
                 new Student()
                {
                    Id=1,
                    Name="白開水",
                    ClassId=2,
                    Age=27
                },
                 new Student()
                {
                    Id=1,
                    Name="狼牙道",
                    ClassId=2,
                    Age=26
                },
                new Student()
                {
                    Id=1,
                    Name="Nine",
                    ClassId=2,
                    Age=25
                },
                new Student()
                {
                    Id=1,
                    Name="Y",
                    ClassId=2,
                    Age=24
                },
                new Student()
                {
                    Id=1,
                    Name="小昶",
                    ClassId=2,
                    Age=21
                },
                 new Student()
                {
                    Id=1,
                    Name="yoyo",
                    ClassId=2,
                    Age=22
                },
                 new Student()
                {
                    Id=1,
                    Name="冰亮",
                    ClassId=2,
                    Age=34
                },
                 new Student()
                {
                    Id=1,
                    Name="",
                    ClassId=2,
                    Age=30
                },
                new Student()
                {
                    Id=1,
                    Name="畢帆",
                    ClassId=2,
                    Age=30
                },
                new Student()
                {
                    Id=1,
                    Name="一點半",
                    ClassId=2,
                    Age=30
                },
                new Student()
                {
                    Id=1,
                    Name="小石頭",
                    ClassId=2,
                    Age=28
                },
                new Student()
                {
                    Id=1,
                    Name="大海",
                    ClassId=2,
                    Age=30
                },
                 new Student()
                {
                    Id=3,
                    Name="yoyo",
                    ClassId=3,
                    Age=30
                },
                  new Student()
                {
                    Id=4,
                    Name="unknown",
                    ClassId=4,
                    Age=30
                }
            };
            #endregion
            return studentList;
        }

    }

 

簡單了解委托

 

1. 委托是什么?

其實,我一直思考如何講解委托,才能把委托說得更透徹。說實話,每個人都委托都有不同的見解,因為看問題的角度不同。個人認為,可以從以下2點來理解:

 (1) 從數據結構來講,委托是和類一樣是一種用戶自定義類型

 (2) 從設計模式來講,委托(類)提供了方法(對象)的抽象。

既然委托是一種類型,那么它存儲的是什么數據?

我們知道,委托是方法的抽象,它存儲的就是一系列具有相同簽名和返回回類型的方法的地址。調用委托的時候,委托包含的所有方法將被執行。

 

2. 委托類型的定義

委托是類型,就好像類是類型一樣。與類一樣,委托類型必須在被用來創建變量以及類型對象之前聲明。

delegate void MyDel(int x);

 

委托類型聲明:

(1) 以deleagate關鍵字開頭。

(2)返回類型+委托類型名+參數列表。

 

3. 聲明委托變量

MyDel del1,del2;

 

4. 初始化委托變量

(1) 使用new運算符

new運算符的操作數的組成如下:

  • 委托類型名
  • 一組圓括號,其中包含作為調用列表中的第一個成員的方法的名字。方法可以是實例方法或靜態方法。
del1 = new MyDel( 實例方法名 );
del2 = new MyDel( 靜態方法名 );

(2)使用快捷語法

快鍵語法,它僅由方法說明符構成。之所以能這樣,是因為在方法名稱和其相應的委托類型之間有隱式轉換。

del1 = 實例方法名;
del2 = 靜態方法名;

 

5. 賦值委托

 由於委托是引用類型,我們可以通過給它賦值來改變包含在委托變量中的方法地址引用。舊的引用會被垃圾回收器回收。

MyDel del;
del = 實例方法名; //委托初始化
del = 靜態方法名;//委托重新賦值,舊的引用將被回收

 

6. 委托調用

委托調用跟方法調用類似。委托調用后,調用列表的每個方法將會被執行。

在調用委托前,應判斷委托是否為空。調用空委托會拋出異常,這是正常標准調用

if(null != del)
{
     del.Invoke();//委托調用
}

 

 在net 1.1的時代,我們是使用Invoke()方法類調用委托的。但是微軟在net 2.0 的時候,我們可以這么去調用委托 直接 del();

if(null != del)
{
     del();//委托調用
}

 

 

7. 綜合練習

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    class Program
    {
        public delegate void meathed1();//1 委托的聲明   委托是一種類型 
        public delegate int meathed2();//2 可以聲明在類的內部,也可以聲明在類的外部
        public delegate void meathed3(int id, string name);
        public delegate string meathed4(string msg);
        static void Main(string[] args)
        {
            meathed1 me1 = new meathed1(m1);
            meathed2 me2 = new meathed2(m2);
            meathed3 me3 = new meathed3(m3);
            meathed4 me4 = new meathed4(m4);
            me1.Invoke();
            int result=   me2.Invoke();
            Console.WriteLine(result);
            me3(1, "wbc");
            Console.WriteLine(me4("您吃了嗎"));
            Console.Read();

        }
        public static void m1() {
            Console.WriteLine("我是無參數無返回值的方法");
        }
        public static int m2()
        {
            Console.WriteLine("我是無參數有返回值的方法~~~~~~~~~");
            return 123;
        }
        public static void m3(int id, string name)
        {
            Console.WriteLine($"我是有參數無返回值的方法,id={id},name={name}");
        }
        public static string m4(string sparams)
        {
            Console.WriteLine($"我是無參數無返回值的方法 ,參數{sparams}");
            return "參數返回了";
        }
    }
}

 

8. 多播委托

 多播委托(MulticastDelegate)繼承自 Delegate ,表示多路廣播委托;即,其調用列表中可以擁有多個元素的委托。實際上,我們自定義的委托的基類就是 MulticastDelegate。這句話不好理解,繼續往下看

 

當我們自己寫一個類去繼承Delegate 的時候,開發工具提示我 特殊類型不允許被繼承,估計這是net 唯一一個特殊類型。

 public abstract class myted : Delegate {

        }

 

我們來看一個案列來使用下多播委托

 meathed1 me1 = new meathed1(m1);
            me1 += m2;
            me1 += m3;
            me1.Invoke();
            me1 -= m2;
            me1();

 

 多播委托:

+= 的時候就是在委托的實例之后加入注冊更多的方法,像一個鏈子,執行的時候按加入的先后順序執行

-=的時候就是在委托的實例之后移除注冊更多的方法。從鏈子的尾部開始匹配,找到一個完全吻合的移除,沒有找到不會觸發異常。

注:實例方法因為每一次實例地址的引用不一致,所以無法移除。

 

9. net 框架提供的自帶委托

 

8.1.Func委托類型

Func是有返回值的泛型委托,可以沒有參數,但最多只有16個參數,並且必須要有返回值。

 

Func<string, int> funcDelegate = new Func<string, int>();

 

 2.Action委托類型
Action是沒有返回值的泛型委托,可以沒有參數,但最多只有16個參數。 

 Action<string, string> method = new Action<string, string>();

 

  關於泛型這里就不過多介紹了,本人前邊寫了一篇比較詳細。建議大家以后使用委托的時候,盡量使用net 類庫自帶的委托。

這里擴展下事件的基本使用,事件就是在委托的實例加上一個event 的關鍵字。所以委托是一種類型,事件就是委托類型的實例。這里只是擴展,不懂不影響學習linq to object 

 

 public void Meta() {

        }
        public delegate void meathed1();
        public class Cat {
            public event meathed1 met_event;
            public meathed1 mea = new meathed1(m1);

            public void Run() {
                mea += m2;
                mea.Invoke();
                met_event = new meathed1(m1);
                met_event += m2;
                met_event.Invoke();

            }
            public static void m1() {

            }
            public static void m2() {

            }
        }
View Code

 

調用方法如下 

   Cat c = new Cat();
            c.mea = Meta;
            c.mea.Invoke();

            // c.met_event = meta;這種寫法是不允許的,只有通過訂閱的方式才允許,如下
            c.met_event += new meathed1(Meta);

 

使用事件和委托的區別我們一下子就能區分出來:事件更安全,在類的外部不允許破壞他的原有執行內容,只有在從新訂閱  event關鍵字限制了權限,保證安全

 

匿名方法&lambda表達式

 我們看了上面的委托,發現寫起來好麻煩,並且用起來特別的不方便,有沒有更簡便的方法呢?可不可以把我們的方法寫到委托里面呢?答案是肯定的;這種寫法我們稱為匿名方法。寫法如下:

 Action<string> method = new Action<string>(delegate(string name){
                Console.WriteLine(name);
            });
            method.Invoke("wbc");
            Console.Read();

 

 但是這種寫法還不夠簡便,在.net 2.0的時候,我們發現1.1的時候在頻繁的使用委托,微軟做了很大的改進和更新,新的寫法如下

 Action<string> method = new Action<string>(name =>
                Console.WriteLine(name)
            );
            method.Invoke("wbc");
            Console.Read();

 

 在2.0里面,我們去掉了delegate關鍵字,如果子有一個參數的時候,連括號也省略了,用"=>"指向方法體,這個尖叫號等號我們讀做“goes to”,如果方法只有一句話,連花括號也省略了。參數類型直接就可以推算出來。看一個復雜的案列

  Func<string,int,string> method = new Func<string, int, string>((name,age) => {
                return $"{name}今年{age}歲了";
            }
                
            );
         string result=   method.Invoke("wbc",0);
            Console.WriteLine(result);
            Console.Read();

其實我們的匿名方法就是lambda 表達式。

 

匿名類

有了匿名方法一定會有匿名類,在net 3.0的時候才有的匿名類,我們看下在沒有匿名類的時候,我們是如何去寫的。

 object student = new  {
                id=123,
                name="1234"
            };
            Console.WriteLine(student);
            Console.Read();

 

這種寫法只能輸出 “{ id = 123, name = 1234 }”這種結果的Json 數據,並且我們在程序中不能使用student.id,會報錯。看下匿名寫法

        var model = new
            {
                Id = 1,
                Name = "Alpha Go",
                Age = 21,
                ClassId = 2
            };
            Console.WriteLine(model.Age);
            Console.Read();

 我們來看下var 關鍵字,其實var 關鍵字就是一個語法糖,我們不必過於在意,這里就不過多的演示了,我們的根本目的是linq to object

  1. 必須在定義時初始化。也就是必須是var s = “abcd”形式,而不能是如下形式:
   var s;
   s = “abcd”;//這種寫法是不允許的
  2. 一但初始化完成,就不能再給變量賦與初始化值類型不同的值了。
  3. var要求是局部變量。
4. 使用var定義變量和object不同,它在效率上和使用強類型方式定義變量完全一樣。

 這里在簡要擴展點技術點“dynamic”,dynamic的用法和object差不多,區別是dynamic在編譯的時候才聲明的一個類型,但是本人建議在程序開發中盡量避開這個關鍵字,后續MVC 中在詳細介紹,dynamic是net 40里面的關鍵字。

 

擴展方法

 擴展方法必須建立在靜態類中的靜態方法,且第一個參數前邊要加上this 關鍵字,使用擴展方法必須引用擴展方法所在的類的命名空間,如果想要全局使用,可以不帶命名空間。個人建議程序中的擴展方法盡量要帶上命名空間。我們來看下擴展方法的使用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    class Program
    {
       
        static void Main(string[] args)
        {

            int? a = null;
            Console.WriteLine(Helper.ToInt(a));//常規寫法輸出
            Console.WriteLine(a.ToInt());//擴展方法輸出
            if ("1qazwsx1qazwsx1qazwsx1qazwsx".ToLengthInCount(16))
            {
                Console.WriteLine("密碼長度應當小於16個字符");
            }
            Console.Read();
        }
        
    }
    public static class Helper
    {
        /// <summary>
        /// 驗證一個數字是否為空,為空返回-1
        /// 擴展方法:靜態類里的靜態方法,第一個參數類型前面加上this
        /// </summary>
        /// <param name="iParameter"></param>
        /// <returns></returns>
        public static int ToInt(this int? iParameter) {
            return iParameter ?? -1;
        }
        /// <summary>
        /// 驗證一個字符串長度是否大指定長度
        ///  
        /// </summary>
        /// <param name="sParameter"></param>
        /// <returns></returns>
        public static bool ToLengthInCount(this string sParameter, int iCount)
        {
            return sParameter.Length >= iCount ?   true : false;
        }
    }
    
}

 這里簡要說明下,擴展方法的方法名和類內不得方法名相同,會優先選擇類內部的方法,這是以就近原則,去執行的,關於就近原則,請看本人java 相關博客,有介紹

linq to object 

 

1. 什么是linq to object ?

LINQ即Language Integrated Query(語言集成查詢),LINQ是集成到C#和Visual Basic.NET這些語言中用於提供查詢數據能力的一個新特性。linq to object 就是對象查詢體系。

  注:LINQ(發音為Link)

 我們來看一個簡單的列子,用到了我們前邊准備的兩個類。我們要取出學生年齡小於30歲的,我們如果不會linq to object 的寫法如下

 

 LinqShow linq = new LinqShow();
            List<Student> studentList =   linq.GetStudentList();//獲取基礎數據
            List<Student> studentListLessThan30 = new List<Student>();
            foreach (var item in studentList)
            {
                if (item.Age < 30)
                {
                    Console.WriteLine($"name={item.Name},age={item.Age}");
                    studentListLessThan30.Add(item);
                }
            }
            Console.Read();

我們可不可以使用委托去計算呢??

  LinqShow linq = new LinqShow();
            List<Student> studentList =   linq.GetStudentList();//獲取基礎數據
            List<Student> studentListLessThan30 = new List<Student>();
            //Func<Student, bool> predicate = s => s.Age < 30;//寫法一,
            //var list = studentList.Where<Student>(predicate);//linq 

            var list = Enumerable.Where(studentList, s => s.Age < 30);//使用lambda 表達式寫法
            foreach (var item in list)
            {
                Console.WriteLine($"Name={item.Name} Age={item.Age}");
            }
            Console.Read();

兩段代碼我就不比較了,這里我們只來剖析下 studentList 的Where方法,我們可以轉移到定義看下,其實where 里面的參數,如下圖

  我們會發現,這不List<T>自帶的一個方法,而是一個擴展方法,這個擴展方法在”System.Linq“命名空間的“public static class Enumerable”類下,也就是說根據擴展方法的特點,我們只要引用了“System.Linq”命名空間,就能使用這個擴展方法,這個擴展方法真的第二個參數是一個Func<TSource, bool> 委托,這個委托有一個TSource泛型參數,一個bool返回值。這就說明了我們上面的lambda 表達式是成立的。

   繼續剖析,我們接受的時候,沒有使用List<Student>接收返回的集合,這是因為我們的linq 只有遇到循環的時候或遇到ToList<T>()的時候才會去迭代,這里涉及到迭代器的問題,后續我們會詳細講解迭代器的使用,或者大家去網上查詢下設計模式迭代器。這么說,我們很難理解。我們通過一個案列來分析,自己寫一個for 循環的擴展方法,對比下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    class Program
    {
       
        static void Main(string[] args)
        {
            LinqShow linq = new LinqShow();
            List<Student> studentList =   linq.GetStudentList();//獲取基礎數據
            //var list = Enumerable.Where(studentList, s => s.Age < 30);//使用lambda 表達式寫法,
            Console.WriteLine($"-------------------------linq寫法-----------------------------------");
            var list = studentList.Where( s => {
                Console.WriteLine($"我的年齡是{s.Age}");
               return s.Age < 30;
            });//使用lambda 表達式寫法,這里在每一次計算,我們都輸出一個我的年齡是,這里不再使用 Enumerable 去調用where ,反正是擴展方法,用法是一樣的
            foreach (var item in list)
            {
                Console.WriteLine($"Name={item.Name} Age={item.Age}");
            }
            Console.WriteLine($"-------------------------自定義擴展方法寫法-----------------------------------");
            var Wbclist = studentList.WbcWhere(s => {
                Console.WriteLine($"Wbc我的年齡是{s.Age}");
                return s.Age < 30;
            });// 自定義擴展方法寫法 這里不再使用 Enumerable 去調用where 
            foreach (var item in Wbclist)
            {
                Console.WriteLine($"Name={item.Name} Age={item.Age}");
            }
            Console.Read();
        }
        
    }
    public static class Helper
    {
        /// <summary>
        /// 自定義擴展方法計算集合
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="source"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static IEnumerable<TSource> WbcWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
        {
            //常規情況下數據過濾
            List<TSource> studentListLessThan30 = new List<TSource>();
            foreach (var student in source)
            {
                //if (item.Age < 30)
                if (predicate(student))
                {
                    studentListLessThan30.Add(student);
                }
            }
            return studentListLessThan30;


        }
        /// <summary>
        /// 驗證一個數字是否為空,為空返回-1
        /// 擴展方法:靜態類里的靜態方法,第一個參數類型前面加上this
        /// </summary>
        /// <param name="iParameter"></param>
        /// <returns></returns>
        public static int ToInt(this int? iParameter) {
            return iParameter ?? -1;
        }
        /// <summary>
        /// 驗證一個字符串長度是否大指定長度
        ///  
        /// </summary>
        /// <param name="sParameter"></param>
        /// <returns></returns>
        public static bool ToLengthInCount(this string sParameter, int iCount)
        {
            return sParameter.Length >= iCount ?   true : false;
        }
    }
    
}

我們來看下輸入結果

 

通過執行結果,我們會發現,自定義的擴展方法是循環一遍,統一輸出的,沒有延遲,而linq 的寫法有延遲。這也就是我們linq 核心之一 “linq 是有延遲的”。這里特別說明,linq 的延遲加載和EF ,ORM 的延遲加載,不是一會事,這里說的延遲,指的是當用到某條數據的時候,才會去查詢。

2. 高逼格裝X時刻

我們以llambda表達式去寫linq 稱為 查詢運算符寫法,下邊我們 使用 查詢表達式去寫,注意區分什么是查詢表達式,什么是查詢運算符寫法,我面試的很多人都說反了。

案列一 多條件過濾篩選數據

  LinqShow linq = new LinqShow();
            List<Student> studentList =   linq.GetStudentList();//獲取基礎數據
                //查詢運算符
                var list1 = studentList.Where<Student>(s => s.Age < 30 && s.Name.Length > 3);//陳述句
                foreach (var item in list1)
                {
                    Console.WriteLine("Name={0}  Age={1}", item.Name, item.Age);
                }
             
                //查詢表達式
                Console.WriteLine("********************");
                var list2 = from s in studentList
                           where s.Age < 30 && s.Name.Length > 3
                           select s;
                foreach (var item in list2)
                {
                    Console.WriteLine("Name={0}  Age={1}", item.Name, item.Age);
                }
             
            Console.Read();

 我們會發現,表達式的寫法是不是很像sql ,語法以from  對象 ... in .. where  select 對象  ,是不是很高大上,夠逼咯吧!!!!!!,下邊在簡單介紹幾個案列

案列二將篩選數據付給匿名類或者實體類

 {
                    Console.WriteLine("********************");
                    var list = studentList.Where<Student>(s => s.Age < 30)
                                         .Select(s => new
                                         {
                                             IdName = s.Id + s.Name,
                                             ClassName = s.ClassId == 2 ? "高級班" : "其他班"
                                         });
                    foreach (var item in list)
                    {
                        Console.WriteLine("Name={0}  Age={1}", item.ClassName, item.IdName);
                    }
                }
                {
                    Console.WriteLine("********************");
                    var list = from s in studentList
                               where s.Age < 30
                               select new
                               {
                                   IdName = s.Id + s.Name,
                                   ClassName = s.ClassId == 2 ? "高級班" : "其他班"
                               };

                    foreach (var item in list)
                    {
                        Console.WriteLine("Name={0}  Age={1}", item.ClassName, item.IdName);
                    }
                }

 

案列三 排序分頁

   var list = studentList.Where<Student>(s => s.Age < 30)
                                         .Select(s => new
                                         {
                                             Id = s.Id,
                                             ClassId = s.ClassId,
                                             IdName = s.Id + s.Name,
                                             ClassName = s.ClassId == 2 ? "高級班" : "其他班"
                                         })
                                         .OrderBy(s => s.Id)//首先升序排序
                                         .OrderByDescending(s => s.ClassId)//首先降序排序
                                         .ThenBy(s => s.ClassId)//升序降序排序以后再排序
                                         .Skip(2)//跳過  分頁
                                         .Take(3)//獲取數據
                                         ;
                    foreach (var item in list)
                    {
                        Console.WriteLine($"Name={item.ClassName}  Age={item.IdName}");
                    }

 

 案列四 關聯查詢

 首先添加班級數據 

 List<Class> classList = new List<Class>()
                {
                    new Class()
                    {
                        Id=1,
                        ClassName="初級班"
                    },
                    new Class()
                    {
                        Id=2,
                        ClassName="高級班"
                    },
                    new Class()
                    {
                        Id=3,
                        ClassName="微信小程序"
                    },
                };
View Code

 

 表達式寫法

  var list = from s in studentList
                           join c in classList on s.ClassId equals c.Id
                           select new
                           {
                               Name = s.Name,
                               CalssName = c.ClassName
                           };
                foreach (var item in list)
                {
                    Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}");
                }

 

算數運輸符寫法

  var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new
                {
                    Name = s.Name,
                    CalssName = c.ClassName
                });
                foreach (var item in list)
                {
                    Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}");
                }

 在linq  中沒有右鏈接,如果要寫右鏈接,只能反過來寫,這里不過多去寫案列了,這種案列網上很多,一會總結過后,分享幾篇

 

總結及推薦

 1.匿名函數只能使用在委托中

2.var 關鍵字的特點,如何使用var 實現匿名類。

3. lambda 表達式

4.擴展方法

5.運算符linq  寫法

6.表達式linq 寫法

7 linq 的延遲加載原理

分享 1 linq 案列 ,很全面的

分享2  linq  標准查詢操作符  很全面的 共五篇


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM