Linq是C#集成的查询语法,对继承IEnumerable<T>对象进行查询。至于什么是IEnumerable<T>接口,简单来说就是继承该接口的对象可以进行迭代(可以使用foreach)。
如果想要使用linq,只需要using System.linq,在此命名空间中提供了对linq相关关键字的定义。例如现在有一个Car类,里面包含了Name和Mileage两个数据成员。
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的原理是什么呢?
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的原因,这些拓展方法全部都在上述命名空间之中。
实现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);④ foreach与tield return
foreach是遍历循环,在C#中只要实现了IEnumerable接口,就可以使用foreach进行遍历操作。yield return 也是基于此点对结果进行返回。普通的return语句碰到时该函数直接结束,但是yield return并不相同,在foreach循环之中该语句将返回本轮循环的结果,并进行下一轮循环直到遍历完成,最后将所有yield return返回的值合并在一起组成一个IEmerable<T>的对象返回。这也是为什么在SelectMe中yield 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);
}
}