主要内容概要
1 匿名方法 lambda表达式
2 匿名类 var 扩展方法
3 linq to object
匿名方法
lambda演变历史
首先我们定义了如下这么些委托,还有DoNothing和Study方法:
public delegate void NoReturnNoPara();
public delegate void NoReturnWithPara(string x, string y);//1 声明委托
public delegate int WithReturnNoPara();
public delegate string WithReturnWithPara(out int x, ref int y);
第一阶段: .NetFramework1.0/1.1时代,是像下面这么写的:
NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);
NoReturnWithPara noReturnWithPara = new NoReturnWithPara(this.Study);
noReturnWithPara.Invoke("Chaoqiang", "ZhengChaoqiang");
这时候是需要先定义一个方法DoNothing,然后进行绑定到委托的。
第二阶段: .NetFramework2.0 匿名方法,增加了一个delgate关键字 可以访问局部变量
int i = 10;
NoReturnWithPara noReturnWithPara = new NoReturnWithPara(delegate (string x, string y)
{
Console.WriteLine(i);//匿名方法可以访问局部变量
Console.WriteLine("This is Study");
});
noReturnWithPara.Invoke("Chaoqiang", "ZhengChaoqiang");
第三阶段: .NetFramework3.0 去掉delegate关键字,添加了一个=> goes to
NoReturnWithPara noReturnWithPara = new NoReturnWithPara((string x, string y) =>
{
Console.WriteLine(i);
Console.WriteLine("This is Study");
});
noReturnWithPara.Invoke("Chaoqiang", "ZhengChaoqiang");
更进一步的优化,省略参数类型,是编译器的语法糖(根据委托约束的):
NoReturnWithPara noReturnWithPara = new NoReturnWithPara((x, y) =>
{
Console.WriteLine(i);
Console.WriteLine("This is Study");
});
noReturnWithPara.Invoke("Chaoqiang", "ZhengChaoqiang");
如果方法体只有一行,可以去掉大括号和分号去掉,new NoReturnWithPara也可以省略:
NoReturnWithPara noReturnWithPara = (x, y) => Console.WriteLine("This is Study");
noReturnWithPara.Invoke("Chaoqiang", "ZhengChaoqiang");
针对一些特殊情况:
Action<string, string> action = (x, y) => Console.WriteLine($"{x}_{y}");
Action<string> action1 = x => Console.WriteLine($"{x}"); // 如果只有一个参数的时候,可以省略小括号
Func<string, string> func = x => "杰克"; //如果有返回值,而且方法体只有一行,可以省略return;
Func<string> func1 = () => "杰克"; //如果有返回值,而且方法体只有一行,可以省略return;
lambda表达式就是什么?
其实是一个方法, 在中间语言中,为其分配了一个方法名称;
通过反编译之后,发现Lambda表达式生成在一个名称为<> 密封类中的一个方法;
lambda在多播委托中的应用
NoReturnWithPara noReturnWithPara = new NoReturnWithPara(this.Study);
noReturnWithPara += this.Study;
noReturnWithPara += (x, y) =>
{
Console.WriteLine($"{x}_{y}");
};
noReturnWithPara -= this.Study;
noReturnWithPara -= (x, y) => { Console.WriteLine($"{x}_{y}"); }; //lambda表达式在多播委托中不能被移除
noReturnWithPara("Chaoqiang", "ZhengChaoqiang");
lambda表达式在多播委托中不能被移除,因为在编译器中生成了两个不同的方法。
匿名类
第一阶段:3.0出了个匿名类
object model = new//3.0
{
Id = 2,
Name = "undefined",
Age = 25,
ClassId = 2,
Teacher = "Richard"
};
这时候我们发现,是无法访问属性值。object中肯定有Id/Name,C#是强类型语言,编译器不认可,
//会出错
Console.WriteLine(model.Id);
Console.WriteLine(model.Name);
第二阶段:4.0的时候出了dynamic这个动态类型,可以解决上面匿名类的不能访问属性的问题,因为dynamic避开编译器检查。
dynamic dModel = new//
{
Id = 2,
Name = "undefined",
Age = 25,
ClassId = 2
};
dModel.Id = 134;
Console.WriteLine(dModel.Id);
Console.WriteLine(dModel.Name);
// Console.WriteLine(dModel.Js);// 编译没问题,运行会报异常
第三阶段:var语法糖
利用var构造匿名类:
var model1 = new //不能声明方法
{
Id = 2,
Name = "undefined",
Age = 25,
ClassId = 2,
Teacher = "Richard"
};
Console.WriteLine(model1.Id);
Console.WriteLine(model1.Name);
model1.Id=3;//会出错
需要注意的是:var匿名不能声明方法,字段也只有在初始化的时候指定。
1 var 配合匿名类型使用
2 var 偷懒,复杂类型的使用
int i2 = 2;
var i1 = 1;//var就是个语法糖,由编译器自动推算
var s = "Richard";
//var aa;//var声明的变量必须初始化,必须能推算出类型
扩展方法
扩展方法:添加一个第三方静态类,把当前需要扩展的类作为参数添加方法,在第一个参数前加一个this
下面我们先看一个实例,一开始有个Student类:
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}我们一起学习.net开发", this.Id, this.Name);
}
public void StudyHard()
{
Console.WriteLine("{0} {1}我们一起学习.net开发", this.Id, this.Name);
}
}
随着业务的修改,如果我们需要增加一些方法,会频繁修改原来的代码,代码就很不稳定。这时候,常见的方法就是增加一个扩展方法,可以像下面这么来操作:
首先有个静态类,然后有个静态方法,再在第一个参数前面加上this
public static class ExtendMethod
{
public static void StudyPractise(this Student student)
{
Console.WriteLine("{0} {1}我们一起学习.net开发-扩展方法"", student.Id, student.Name);
}
}
这样一来,我们可以在外面调用这个扩展方法,看起来就像和实例方法是一样的:
Student student = new Student()
{
Id = 1,
Name = "Mr.zhang",
Age = 25,
ClassId = 2
};
student.Study();
student.StudyPractise(); //扩展方法;看起来跟实例方法调用方式一样
扩展方法调用,用起来很像实例方法,就像扩展了Student的逻辑,一般来说,有以下场景:
- 第三方的类,不适合修改源码,可以通过扩展方法增加逻辑;需要注意的是:有限调用实例方法,最怕扩展方法增加了,然后原来的类后来有被修改了。
- 适合组建时开发的扩展(。NET Core),定义接口或者类,是按照最小需求,但是在开发的时候又经常需要一些方法,就是通过扩展方法context.Response.WriteAsync 中间件的注册。
- 扩展一些常见的操作
例如下面对int的一个非空判断扩展方法:
public static int ToInt(this int? k)
{
return k ?? 0;
//if (k == null)
//{
// return 0;
//}
//else
//{
// return k.Value;
//}
}
用的时候就可以直接int x = k.ToInt() + y;
这么来用。
又例如对string做一个扩展方法,用于截取特定长度的字符串:
public static string ToStringNew(this string str, int length = 5)// 默认参数
{
if (string.IsNullOrWhiteSpace(str))
{
return string.Empty;
}
else if (str.Length > length)
{
return $"{str.Substring(0, length)}....";
}
else
{
return str;
}
}
用的时候可以这么来用:
string str = "中华人名共和国万岁";
string str1 = "中华人名共和国...";
str1.ToStringNew();
又例如泛型扩展方法,对任意类型都可以增加一个扩展方法:
public static string GenericExtend<T>(this T t)// 默认参数
{
if (t is Guid)
{
return t.ToString().Replace("-","");
}
///.....
else
{
return t.ToString();
}
}
用的时候可以这么来写str1.GenericExtend
,对object进行扩展的时候会污染基础类型,一般少为object ,没有约束的泛型去扩展。
思考:匿名方法或者lamdba表达式是不是只能是无返回值的
Action action = () => { };
Action<string> action1 = s => Console.WriteLine("test");
Func<int> func0 = () => DateTime.Now.Day;//如果方法只有一行,去掉大括号分号return
int iResult = func0.Invoke();
Func<int,int> func1 = (s) => s;
iResult=func1.Invoke(1);
Linq
Linq To Object
.NetFramework3.0的一个非常重大的改变
Linq专门用来做数据处理
Where筛选
先看一个例子体会一下,首先有个student的列表List<Student> studentList = new List<Student>()
,然后要做一些数据过滤,正常来说,下面两个都是常见的操作。
{
var list = studentList.Where<Student>(s => s.Age < 30);
foreach (var item in list)
{
Console.WriteLine("Name={0} Age={1}", item.Name, item.Age);
}
}
{
Console.WriteLine("********************");
var list = from s in studentList
where s.Age < 30
select s;
foreach (var item in list)
{
Console.WriteLine("Name={0} Age={1}", item.Name, item.Age);
}
}
看到这里的时候,可能会赞叹,这个Where<Student>
真不错,不仅见名知意,并且代码简介。我们先通过委托泛型表达式的知识想一想,我们自己要实现这个扩展方法,可以怎么做?
public static List<T> JerryWhere<T>(this List<T> resource, Func<T, bool> func)
{
var list = new List<T>();
foreach (var item in resource)
{
if (func.Invoke(item)) // /* if (item.Age < 30)*///bool //item做为一个参数 返回一个bool
{
list.Add(item);
}
}
return list;
}
这个扩展方法就是传递一个Func委托进去,然后逐个遍历,通过委托的方法去判断是否添加到list里面去,最后统一返回一个list容器。
更进一步的,这里的List还不够generic,我们可以采用这个IEnumerable,并结合迭代器(模式),写成下面这样:
public static IEnumerable<T> JerryWhereIterator<T>(this IEnumerable<T> resource, Func<T, bool> func)
{
foreach (var item in resource)
{
Console.WriteLine("开始检查");
Thread.Sleep(300);
if (func.Invoke(item))
{
yield return item; // yield 和Enumerabl 是配合使用,按需获取/延时加载
}
}
}
通过在foreach中加Console.WriteLine("开始检查")
可以发现,上述两种方法,前者是全部检查完了再返回,后者是按需获取,在用的时候采取执行,也就是延迟加载,逐个返回的。
后者的另外一个优点是只要实现了IEnumerable接口的类型,都可以用,例如数据,集合等。
Select投影
例如在上例中,筛选出的一堆学生要转换成另一个对象:
写法一:
//var 在这里是为了匿名类
var list = from s in studentList
where s.Age < 30
select new
{
IdName = s.Id + s.Name,
ClassName = s.ClassId == 2 ? "Normal Class" : "Other Class"
};
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 ? "Normal Class" : "Other Class"
})
.OrderBy(s => s.Id)//排序
.OrderByDescending(s=>s.ClassId)//降序 只会生效最后一个
//.ThenBy//2个都生效
.OrderByDescending(s => s.ClassId)//倒排 只会生效最后一个
.Skip(2)//跳过几条
.Take(3)//获取几条
;
分组group by
同样有两种写法,首先筛选年级小于30的学生集合s,其次按照学生的班级进行分组,得到sg。
方法一:
var list = from s in studentList
where s.Age < 30
group s by s.ClassId into sg
select new
{
key = sg.Key,
maxAge = sg.Max(t => t.Age)
};
foreach (var item in list)
{
Console.WriteLine($"key={item.key} maxAge={item.maxAge}");
}
方法二:
var list = studentList.GroupBy(s => s.ClassId).Select(sg => new
{
key = sg.Key,
maxAge = sg.Max(t => t.Age)
});
foreach (var item in list)
{
Console.WriteLine($"key={item.key} maxAge={item.maxAge}");
}
值得注意的是,这里的sg
,实现了IEnumerable
接口,可以进行foreach操作;并且这个key就是班级编号,可以这么进行验证:
foreach(var data in sg)
{
Console.WriteLine(data.Key);
foreach (var item in data)
{
Console.WriteLine($"{item.Id} {item.Name} {item.Age}");
}
}
join查询
多表查询join学生的班级关联班级,然后组成匿名类,包含学生姓名,班级名称:
方法一:
var list = from s in studentList
join c in classList on s.ClassId equals c.Id //条件的比较 只能使用 equals 不能用==
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}");
}
左连接
从学生列表的班级Id去join班级,是以学生列表为准,如果学生列表中的班级ID在班级列表中没有,则班级为空。
方法一:
var list = from s in studentList
join c in classList on s.ClassId equals c.Id
into scList
from sc in scList.DefaultIfEmpty()// 在Linq语句中只有左连接 DefaultIfEmpty 关键字
select new
{
Name = s.Name,
CalssName = sc == null ? "无班级" : sc.ClassName//c变sc,为空则用
};
foreach (var item in list)
{
Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}");
}
Console.WriteLine(list.Count());
方法二:
var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new
{
Name = s.Name,
CalssName = c.ClassName
}).DefaultIfEmpty();//为空就没有了
总结一下:
Linq to object
在Enumerable类,正对满足IEnumerable数据,指的是内存数据
Linq–Where: 把对数据过滤的通用操作完成,把可变的过滤逻辑交给委托;
Linq–Select:把对数据转化的通用操作完成,把可变的转换逻辑交给委托;
Linq–OrderBy,…
Linq其实就是把对数据操作的通用部分完成,把可变的交给委托,使用者只关心可变部分即可,Linq就是这样一个封装。
Linq to SQL
针对IQueryable数据,操作数据库
程序访问数据库,都需要Ado.net+Sql
封装一下通用数据库操作,可变的SQL语句,是通过表达式目录树来传递,这个可以被解析的。
IQueryable<Student> list = studentList.AsQueryable();
list.Where<Student>(s => s.Age > 30);
需要注意的是:这里传递的是表达式目录树,不是匿名方法,需要可以被解析的。
Linq to XML
封装与喜爱通用的对XML文件的操作,可变的操作通过委托来传递
这是一种伟大的封装思想,希望通过一种模式完成一切数据源的访问。