C#Linq自定义拓展

Linq是C#集成的查询语法,对继承IEnumerable<T>对象进行查询。至于什么是IEnumerable<T>接口,简单来说就是继承该接口的对象可以进行迭代(可以使用foreach)。

如何使用Linq

如果想要使用linq,只需要using System.linq,在此命名空间中提供了对linq相关关键字的定义。例如现在有一个Car类,里面包含了NameMileage两个数据成员。

public class Car{
public string Name{get;set;}
public double Mileage{get;set;}
}

现在我想从一个List<Car>中查询出Name为Mercedes的汽车,并按照里程数降序按照降序排列。那么如何使用Linq查询?

//对该list对象进行查询
List<Car> lists=new List<Car>
{
   //many CarDetails
}

var result=from item in lists
           where item.Name=="Mercedes" 
           orderby item.Mileage descending
           select item;

这种类似于Sql语句的查询给我们对数据集的相关操作提供了极大的方便,那么linq的原理是什么呢?

Linq的原理

1.拓展方法

  • C#作为完全面向对象的语言,拓展方法是一个很常见的操作。那么什么是拓展方法?

——通常是用户自定义的方法,并且希望通过点运算符点出自定义的方法。

比如string类,这个类已经被封装,用户不能在封装好的类中再添加方法。如果要输出字符串,直接使用Console.WriteLine(str);语句即可输出。

string str="helloworld";
//输出str
Console.WriteLine(str);
//如何使用点运算符,使以下语句达到相同效果呢?
//——使用拓展方法即可达成目标
//str.Print();

但现在我觉得这样输出很麻烦,如果string有一个方法Print(),我可以直接str.Print()就可以实现对字符串的输出。这时候我就可以写一个拓展方法来完成这个工作。

拓展方法要写在静态类的静态方法,同时第一个参数要加上this关键字修饰表示此参数为调用对象类型。

public static class StringExtensions
{
      //这里的this关键字表示该方法可以被一个string类型的对象调用
      public static void Print(this string str)
      {
            Console.WriteLine(str);
      }
}

//如果要输出str的值,此时直接调用str.Print()就可以完成输出

在使用linq的时候,编译器会调用方法进行查询而不是linq查询。Linq使用关键字封装了对方法的调用。Linq为IEnumerable<T>接口提供了大量的拓展方法,用户可以在任意实现此接口的集合上使用linq查询。这也是为什么使用linq时必须using System.linq的原因,这些拓展方法全部都在上述命名空间之中。

2.如何实现自己版本的linq?

  • 实现Select<T>

① 关注函数的签名SelectMe<T,TResult>(this IEnumerable<T> ts, Func<T,TResult> func),函数名为SelectMe。尖括号里两个参数T,TResult 表示两个泛型模板,当函数接收参数时泛型参数会被实例化。

② 第一个使用this修饰的IEnumerable<T>参数表示继承该接口的类可以点出该拓展方法。

③ 第二个参数Func<T,TResult>,表示一个接受一个T 类型的参数,返回一个TResult类型的委托。

那么什么是委托?简而言之委托可以认为是一个匿名函数,它描述了接受什么类型的数据,进行什么操作,返回什么类型的结果,我们只关心函数的逻辑操作而不关注函数的名称等。如果你学过c/cpp,那么委托就类似于其中的函数指针,如果你对cpp11标准有所了解,那么其中的lambda表达式也可以代替函数指针完成该项工作。在C#中,委托进行了更好的封装,其中Func就是对有返回值委托的一个很好的封装。如果想要自定义委托的话,可以使用delegate关键字进行委托自定义。

这里举一个委托的小栗子:

现在我要写一个函数,接受一个int类型参数,同时接受一个操作方法对该int类型变量进行操作,随后进行输出。那么我就可以这么写:

至于这里的t=>t*2,这个东西叫做lambda表达式,他的标准形式为(//arguments list)=>{//dosomething},更加深入的使用和了解可以去[Docs里了解](https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions)

void func(int val,Func<int,int> func)
{
     Console.WriteLine(func(val));
}
//输出二倍的val,该函数调用后输出10
func(5,t=>t*2);
//输出val^2+val+1,这里输出7
func(2,t=>t*t+t+1);

foreachtield return

foreach是遍历循环,在C#中只要实现了IEnumerable接口,就可以使用foreach进行遍历操作。yield return 也是基于此点对结果进行返回。普通的return语句碰到时该函数直接结束,但是yield return并不相同,在foreach循环之中该语句将返回本轮循环的结果,并进行下一轮循环直到遍历完成,最后将所有yield return返回的值合并在一起组成一个IEmerable<T>的对象返回。这也是为什么在SelectMeyield return最终返回了一个IEnumerable<TResult>类型的对象。

yipublic static class LinqExtension
{
    public static IEnumerable<TResult> SelectMe<T,TResult>(this IEnumerable<T> ts, Func<T,TResult> func)
    {
        foreach (var item in ts)
        {
            yield return func(item);
        }
    }
}
  • 其余要求的实现:

这里使用了where对泛型参数T进行限制的语句,可以指定泛型参数T必须继承自某个接口。[关于where的更多信息点这里](https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/where-generic-type-constraint)

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

public static class LinqExtension
{
    public interface IScore
    {
        public int Score { get; set; }
    }
    public static IEnumerable<TResult> SelectMe<T,TResult>(this IEnumerable<T> ts, Func<T,TResult> func)
                where T : IScore
    {
        foreach (var item in ts)
        {
            yield return func(item);
        }
    }
    public static IEnumerable<T> WhereMe<T>(this IEnumerable<T> ts, Func<T, bool> func)
                where T : IScore
    {
        foreach (var item in ts)
        {
            if (func(item))
            {
                yield return item;
            }
        }
    }
    public static T FirstMe<T>(this IEnumerable<T> ts, Func<T, bool> func)
                where T : IScore
    {
        foreach (var item in ts)
        {
            if (func(item))
                return item;
        }
        return default(T);
    }
    public static int CountMe<T>(this IEnumerable<T> ts, Func<T, bool> func)
        where T:IScore
    {
        int count = 0;
        foreach(var item in ts)
        {
            if (func(item))
            {
                ++count;
            }
        }
        return count;
    }
    public static IEnumerable<T> OrderByScore<T>(this IEnumerable<T> ts, Func<T, bool> func)
    where T : IScore
    {
        return ts.OrderBy(t => t.Score);
    }
}
public class program
{
    public class Point:LinqExtension.IScore
    {
        public string Name { get; set; }
        public int Score { get; set; }

    }
    public static void Main()
    {
        List<Point> vs = new List<Point>()
        {
           new Point{Name="point1",Score=10},
           new Point{Name="222",Score=100}           
        };
        var result = vs.CountMe(t => t.Score > 9);
        //foreach(var item in result)
        //{
        //    Console.WriteLine(item);
        //}
        Console.WriteLine(result);

    }
}