一、Lambda与Linq
1.本质
- Lambda的表达式:()=>{}
- Lambda的本质:一个匿名函数,在底层,会在一个尖括号(<>)类中,生成带有名称的方法。
- Linq的本质(代码的封装思想):从技术上说:是通过扩展方法来完成的,从程序设计上说:把不变的业务逻辑转移,固定的业务逻辑封装,整合起来就是Linq。
2.Lambda的前世今生
.NetFramework2.0 匿名方法 增加了一个delegate关键字,可以访问到除了参数以外的局部变量
.NetFramework3.0 去掉delegate关键了,在参数的后增加了一个=> goes to
.NetFramework3.0后期,去掉了匿名方法红的参数类型,编译器提供了语法糖,可以推导出类型的参数
namespace MyLambdaLinq
{
public delegate void NoReturnNoParaOutClass();
public delegate void GenericDelegate<T>();
public class LambdaShow
{
public delegate void NoReturnNoPara();
public delegate void NoReturnWithPara(int x, string y);//1 声明委托
public delegate int WithReturnNoPara();
public delegate string WithReturnWithPara(out int x, ref int y);
public void Show()
{
//Lambda前世今生;
{
//.Netframework1.0/1.1时代
int j = 0;
NoReturnNoPara method = new NoReturnNoPara(DoNothing);
NoReturnWithPara method1 = new NoReturnWithPara(Study);
method1.Invoke(123, "HelloWord");
}
//.NetFramework2.0 匿名方法 增加了一个delegate关键字,可以访问到除了参数以外的局部变量
int i = 0;
{
NoReturnWithPara method = new NoReturnWithPara(delegate (int x, string y)
{
Console.WriteLine(x);
Console.WriteLine(y);
Console.WriteLine(i);
});
}
//.NetFramework3.0 去掉delegate关键了,在参数的后增加了一个=> goes to
{
NoReturnWithPara method = new NoReturnWithPara((int x, string y) =>
{
Console.WriteLine(x);
Console.WriteLine(y);
Console.WriteLine(i);
});
}
{
//.NetFramework3.0 后期,去掉了匿名方法红的参数类型--为什么可以去掉? 语法糖:编译器提供的便捷功能;可以推导出类型的参数
NoReturnWithPara method = new NoReturnWithPara((x, y) =>
{
Console.WriteLine(x);
Console.WriteLine(y);
Console.WriteLine(i);
});
}
{
//如果匿名方法体中只有一行代码,可以省略方法体的大括号;
NoReturnWithPara method = new NoReturnWithPara((x, y) => Console.WriteLine(x));
NoReturnWithPara method1 = (x, y) => Console.WriteLine(x);
}
}
private void DoNothing()
{
//如果要在这里调用变量j
Console.WriteLine("This is DoNothing");
}
private void Study(int id, string name)
{
Console.WriteLine($"Id:{id},Nam: {name}");
}
}
}
二、匿名类
Object:因为C#是强类型语言,Object需要在编译时才能确定类型。
--定义的字段无法被调用
dynamic:可以避开编译器的检查。
--定义的字段可以被调用
--调用没有定义的字段在运行前也不会出异常
--可给字段重新赋值。
var:弱类型,编译时确定类型。
--定义的字段可以被调用
--调用的字段没有定义的话会出现异常
--无法给字段重新赋值
--无法声明方法
--无法赋值为null,必须进行初始化赋值,且赋的值必须让能推算出类型。
--不允许作为方法的参数的类型使用
(建议var使用场景:需要使用到匿名类的时候,在不确定具体什么类型的时候,复杂类型的时候。缺点是:代码可读性变差,开发中建议尽量明确类型)
示例:
三、扩展方法
扩展:扩充,让现有的功能更强大,增加原本不存在的功能
扩展方法:在不修改原有类的情况下,增加一个新的方法,并且可以像实例方法一样调用。
扩展方法需要满足的条件:
1.必须是静态类中的静态方法
2.扩展的是谁,谁就要作为第一个参数,且这个参数前面需要增加this关键字
应用场景:
1.扩展第三方类库,第三方类库多是引用dll方式引入,无法直接修改代码,可以使用扩展方法给第三方类库增加新功能。
2.祖传项目维护,本着老代码能不动就不动的原则(老代码一般补丁代码过多,可能修改一个地方就会导致其他地方出现问题),使用扩展方法进行新功能增加。
1.普通的扩展方法
现有的一个学生类:
/// <summary>
/// 学生类
/// </summary>
public class Student
{
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
/// <summary>
/// 名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// 学习方法
/// </summary>
public void Study()
{
Console.WriteLine($"{Age}岁的{Name}在这里是学习");
}
}
定义一个扩展类:
此处的StudentExtension为静态方法,StudyMore方法的参数前增加了this关键字
/// <summary>
/// 扩展方法
/// </summary>
public static class StudentExtension
{
public static void StudyMore( this Student student)
{
Console.WriteLine($"{student.Age}岁的{student.Name}这里是学习更多");
}
}
调用:
static void Main(string[] args)
{
Student student = new Student();
student.Name = "张三";
student.Age = 23;
//Student类中原有的学习方法
student.Study();
//扩展类中新增的扩展方法
student.StudyMore();
}
执行结果:
2.类型的扩展方法(建议使用时指定具体类型进行扩展)
1.普通类型都可以进行方法扩展,示例中的IntToString()方法
2.泛型可以进行方法扩展。但是具有侵入性(表示所有的类型都会拥有这个方法,覆盖面太广),可能会导致一些类型存在了一些不应该存在的行为,如果使用错误便会导致bug发生,如示例3。
3.Object是所有类型的父类,所有也会出现泛型的问题。
4.如果版本升级导致dll等引用中,增加了扩展方法,同时也在类的内部增加了一个同样的方法,在调用的时候,会优先调用类内部声明的方法;
演示用的扩展类:
public static class ExtensionMethod
{
/// <summary>
/// 示例1:int的扩展方法
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public static string IntExtension(this int i)
{
return i.ToString();
}
/// <summary>
/// 示例2:泛型的扩展方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static string GenericsExtension<T>(this T t)
{
return t.ToString();
}
/// <summary>
/// 示例3:泛型的扩展方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static int GenericsExtension2<T>(this T t)
{
return Convert.ToInt32(t);
}
/// <summary>
/// 示例4:Object的扩展方法
/// </summary>
/// <param name="o"></param>
/// <returns></returns>
public static string ObjectExtension(this Object o)
{
return o.ToString();
}
}
调用演示:
static void Main(string[] args)
{
int i = 0;
//示例1:int的扩展方法
i.IntExtension();
//示例2:泛型的扩展方法
i.GenericsExtension<int>();
//示例3:泛型的扩展方法的弊端演示
string s = "传入字符换类型之后这个方法将会报错";
s.GenericsExtension2<string>();
//示例4:Object的扩展方法的弊端演示
i.ObjectExtension();
}
3.实战演示
如果字符串长度超过5,就只取前五个字符
扩展类:
public static class ExtensionMethod
{
/// <summary>
/// 如果字符串长度超过5,就只取前五个字符
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string ToSubString(this string str, int maxLength = 5)
{
//如果字符串为空,返回空
if (string.IsNullOrEmpty(str))
{
return string.Empty;
}
//如果字符串长度大于5截取前五位,否则返回原字符串
if (str.Length > maxLength)
{
return str.Substring(0, 5);
}
else
{
return str;
}
}
}
调用演示:
static void Main(string[] args)
{
// 如果字符串长度超过5,就只取前五个字符
string str1 = "这是六个字哦";
Console.WriteLine($"字符串大于五个字的时候:{str1.ToSubString()}");
string str2 = "这是五个字";
Console.WriteLine($"字符串等于五个字的时候:{str2.ToSubString()}");
string str3 = "这字";
Console.WriteLine($"字符串小于五个字的时候:{str3.ToSubString()}");
string str4 = "";
Console.WriteLine($"字符串为空的时候:{str4.ToSubString()}");
}
执行结果:
四、Linq的原理及示例
Linq to Object:通过方法封装+不变的逻辑封装在方法中,可变的逻辑通过委托传递+扩展方法
Linq to sql:把不变的逻辑封装在内,可变的sql语句通过委托传递
Linq to xml:把不变的处理xml的逻辑封装在内,可变的处理xml的逻辑通过委托传递
等等等
1.使用[扩展方法+委托]模拟Linq的原理
与linq中where的区别: linq的底层是通过迭代器实现循环的
技术手段来说:通过拓展方法来完成的
扩展方法:
public static class ExtensionMethod
{
#region Test1
//public static bool isOk(Student student)
//{
// return student.age < 18;
//}
//static Func<Student, bool> Func = isOk;
#endregion
/// <summary>
/// 按条件筛选学生信息
/// </summary>
/// <param name="studentList"></param>
/// <param name="func">Func<Student,bool> func → 相当于上方Test1的代码</param>
/// <returns></returns>
public static List<Student> FilteringStudentData(this List<Student> studentList, Func<Student, bool> func)
{
List<Student> stuList = new List<Student>();
foreach (var stu in studentList)
{
if (func.Invoke(stu))
{
stuList.Add(stu);
}
}
return stuList;
}
/// <summary>
/// 按条件筛选信息 泛型+委托+扩展方法
/// </summary>
/// <param name="studentList"></param>
/// <param name="func"></param>
/// <returns></returns>
public static List<T> FilteringData<T>(this List<T> old_List, Func<T, bool> func)
{
List<T> list = new List<T>();
foreach (var item in old_List)
{
if (func.Invoke(item))
{
list.Add(item);
}
}
return list;
}
}
调用:
static void Main(string[] args)
{
List<Student> list = new List<Student>()
{
new Student()
{
Name = "张三",
Age = 18
},
new Student()
{
Name = "李四",
Age = 17
},
new Student()
{
Name = "王二",
Age = 19
},
};
//第一种调用方式
Func<Student, bool> func = new Func<Student, bool>(s =>
{
return s.Age < 18;
});
var student = list.FilteringStudentData(func);
//第二种调用方式(第一种的简化)
var stu = list.FilteringStudentData(s => s.Age < 18);
//等同于Linq中的.Where()
var stu_linq = list.Where(s => s.Age < 18);
}
2.改造方法更接近Linq(IEnumerable+yieId)
扩展IEnumerable,使用yieId(状态机的实现)关键字(yieId必须和IEnumerable配套使用)
----IEnumerable通常认为是一个内存数据,类似的还有IQueryable
----yieId关键字做到了按需获取,判断时,只要符合条件就返回,如果不返回就继续往后判断
扩展方法:
public static class ExtensionMethod
{
/// <summary>
/// IEnumerable+YieId
/// </summary>
/// <param name="studentList"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IEnumerable<T> Where_YieId<T>(this IEnumerable<T> old_List, Func<T, bool> func)
{
foreach (var item in old_List)
{
if (func.Invoke(item))
{
yield return item;
}
}
}
}
调用:
static void Main(string[] args)
{
List<Student> list = new List<Student>()
{
new Student()
{
Name = "张三",
Age = 18
},
new Student()
{
Name = "李四",
Age = 17
},
new Student()
{
Name = "王二",
Age = 19
},
};
IEnumerable<Student> stuList = list.Where_YieId(s => s.Age < 18);
foreach (var item in stuList)
{
Console.WriteLine($"名字:{item.Name},年龄:{item.Age}");
}
}
五、Linq常见的语句
除以下常用示例外,关于Linq的文档:Linq相关文档(阿里云盘)
static void Main(string[] args)
{
List<Student> list = new List<Student>()
{
new Student()
{
Name = "张三",
Age = 18,
Class = "一班"
},
new Student()
{
Name = "李四",
Age = 17,
Class = "二班"
},
new Student()
{
Name = "王二",
Age = 19,
Class = "一班"
},
};
#region Linq的两种方式
var stuList1 = list.Where
(
x => x.Age > 18
).ToList();
foreach (var item in stuList1)
{
Console.WriteLine($"stuList1结果展示:Name:{item.Name},Age:{item.Age}");
}
var stuList2 = from s in list
where s.Age > 18
select s; //list里面必然是符合要求的数据;
foreach (var item in stuList2)
{
Console.WriteLine($"stuList2结果展示:Name:{item.Name},Age:{item.Age}");
}
#endregion
#region Linq to Object
#region 投影
var list1 = list
.Where(
x => x.Age > 18//条件过滤
)
.Select(s => new//投影:可以自由组装需要的信息.----通过new一个匿名类或者具体类,定义对象之后进行赋值使用
{
people = s.Name + "-" + s.Age,
describe = s.Age > 18 ? "成年人" : "未成年人"
});
foreach (var item in list1)
{
Console.WriteLine($"list1结果展示:people:{item.people},describe:{item.describe}");
}
var list2 = from s in list
where s.Age > 18 //条件过滤
select new //投影:可以自由组装需要的信息.----通过new一个匿名类或者具体类,定义对象之后进行赋值使用
{
people = s.Name + "-" + s.Age,
describe = s.Age > 18 ? "成年人" : "未成年人"
};
foreach (var item in list2)
{
Console.WriteLine($"list2结果展示:people:{item.people},describe:{item.describe}");
}
#endregion
#region 过滤、排序
var list3 = list
.Where(
x => x.Age > 18 //条件过滤
)
.Select(s => new//投影:可以自由组装需要的信息.----通过new一个匿名类或者具体类,定义对象之后进行赋值使用
{
people = s.Name + "-" + s.Age,
describe = s.Age > 18 ? "成年人" : "未成年人",
Age = s.Age
})
.OrderBy(s => s.Age)//排序 升序
.ThenBy(s => s.describe) //多重排序,可以多个字段排序都生效
.OrderByDescending(s => s.Age)//倒排
.Skip(2)//跳过几条 //必须要先排序
.Take(3)//获取几条 //必须要先排序
;
foreach (var item in list3)
{
Console.WriteLine($"list3结果展示:people:{item.people},describe:{item.describe}");
}
#endregion
#region 分组
var list4 = from s in list
where s.Age > 18 //条件过滤
group s by s.Class into g
select new
{
Class = g.Key,
MaxAge = g.Max(s => s.Age)
};
foreach (var item in list4)
{
Console.WriteLine($"list4结果展示:Class:{item.Class},MaxAge:{item.MaxAge}");
}
var list5 = list.GroupBy(s => s.Class).Select(x => new
{
Class = x.Key,
MaxAge = x.Max(x => x.Age)
});
foreach (var item in list5)
{
Console.WriteLine($"list5结果展示:Class:{item.Class},MaxAge:{item.MaxAge}");
}
#endregion
#region join、left join连接查询
//join
var list6 = from s in list
join c in stuList1 on s.Class equals c.Class //只能使用equals不能使用==
select new
{
Name = s.Name,
Age = s.Age,
Class = s.Class
};
foreach (var item in list6)
{
Console.WriteLine($"list6结果展示:Name:{item.Name},Class:{item.Class}");
}
var list7 = list.Join(stuList1, s => s.Class, c => c.Class, (s, c) => new
{
Name = s.Name,
Age = s.Age,
Class = s.Class
});
foreach (var item in list7)
{
Console.WriteLine($"list7结果展示:Name:{item.Name},Class:{item.Class}");
}
//left join
var list8 = from s in list
join c in stuList1 on s.Class equals c.Class into g
from x in g.DefaultIfEmpty()
select new
{
Name = s.Name,
Age = s.Age,
Class = s.Class,
describe = x == null ? "没有该班级" : "有该班级"
};
foreach (var item in list8)
{
Console.WriteLine($"list8结果展示:Name:{item.Name},Class:{item.Class}");
}
var list9 = list.Join(stuList1, s => s.Class, c => c.Class, (s, c) => new
{
Name = s.Name,
Age = s.Age,
Class = s.Class,
describe = c == null ? "没有该班级" : "有该班级"
}).DefaultIfEmpty();
foreach (var item in list9)
{
Console.WriteLine($"list9结果展示:Name:{item.Name},Class:{item.Class}");
}
#endregion
#endregion
}