C#-LINQ
隐式类型
C#3.0。.NET Fraemork3.5出来的。
编译器自动推算出来类型,语法糖。
#region 隐式类型 var a = 1; var b = "1"; var c = 1.23m; var d = 1.23; Console.WriteLine(a.GetType()); Console.WriteLine(b.GetType()); Console.WriteLine(c.GetType()); Console.WriteLine(d.GetType()); #endregion
关键字var,编译的时候会根据右边的类型判断左边变量的具体类型。
注意:是编译为中间语言的时候确定的。不影响性能。
匿名类型
#region 匿名类型 string Name = "藏锋"; //字段名字,Name根据上面定义的Name起名的。ID是自己定义的名字。 var user = new { Name, ID = 1 }; Console.WriteLine(user.Name); Console.WriteLine(user.ID); #endregion
不用实现写一个类,也可以创建一个对象,并且访问他的成员。
注意:成员名字可以自己定义。 也可以把其他成员放进来(默认名字和其他成员一样,也可以自己在指定)
自动属性
#region 自动属性 属性不能定义在方法里面 int _UserId; public int UserID { get { return _UserId; } set { _UserId = value; } } //自动属性,生成的中间语言和 先定义一个私有变量,在写他的访问器一样。 public string UserNmae { get; set; } #endregion
自动属性在编译的时候生成他对应的私有字段。和get ,set方法。
初始化器
#region 初始化 var stuInit = new Student() { Name = "藏锋", ID = 1 }; IList<int> listInt = new List<int>() { 1, 2, 3, 4 }; //无论是值类型还是引用类型使用的时候都要初始化 赋值。 //值类型一般有默认值0 //引用类型为NULL Student stuNULL; int intNULL; string strNULL; string refStr = string.Empty; int refID =0; Console.WriteLine(refStr); Console.WriteLine(refID); //传出参数使用的时候不需要赋值,原因在方法里面会给他赋值。 string outStr; int outID; stuInit.GetUserName("WYX", ref refStr, ref refID, out outStr, out outID); #endregion
就是在对象创建的时候初始化他的一些值。
委托
#region 委托 Student stuForDele = new Student(); StuDele stuDele = new StuDele(stuForDele.GetUserId); stuDele += stuForDele.GetUserId1; stuDele += stuForDele.GetUserId2; stuDele += stuForDele.GetUserId3; stuDele -= stuForDele.GetUserId3; Console.WriteLine(stuDele(2)); #endregion
委托是一个类型,和CLASS是一个级别的。
泛型
泛型类,泛型方法,泛型委托
#region 泛型 DAO<Teacher> dao = new DAO<Teacher>(); Teacher teac = new Teacher(); var pros1 = dao.GetAllPro(); var pros2 = dao.GetAllPro(teac); Student stRef = new Student(); var typeName = dao.GetTypeName<Student>(stRef); foreach (var item in pros1) { Console.WriteLine(item); } foreach (var item in pros2) { Console.WriteLine(item); } Console.WriteLine(typeName); #endregion
面试经常问的,泛型特点:1,类型安全。2,节约装箱拆箱操作,性能比较好。3,代码重用扩展性强。4,可读性强
1类型安全:用的是定义好的类型,不用object
2性能高:比如说原来一个方法要根据参数做不同的事,参数类型还不一样。那么就只能把参数设置为object,这样就要装箱拆箱操作了。
3代码重用:新建的类型只要符合泛型约束就可以用泛型的方法。
原理:泛型每次编译的时候都会都会生成一套对应的类方法。和在程序里面一个一个写方法一样的。
默认值:通过default关键字,将null赋予引用类型,将0赋予值类型。不能直接给泛型赋NULL或者0,因为不知道他是什么类型。
泛型委托
.NET框架自带三个系统泛型委托
1、public delegate bool Predicate<in T>(T obj);
// // 摘要: // 表示定义一组条件并确定指定对象是否符合这些条件的方法。 // // 参数: // obj: // 要按照由此委托表示的方法中定义的条件进行比较的对象。 // // 类型参数: // T: // 要比较的对象的类型。此类型参数是逆变。即可以使用指定的类型或派生程度更低的类型。有关协变和逆变的更多信息,请参见泛型中的协变和逆变。 // // 返回结果: // 如果 obj 符合由此委托表示的方法中定义的条件,则为 true;否则为 false。
一个传入参数,返回类型为bool
2、public delegate void Action<in T>(T obj);
// // 摘要: // 封装一个方法,该方法只有一个参数并且不返回值。 // // 参数: // obj: // 此委托封装的方法的参数。 // // 类型参数: // T: // 此委托封装的方法的参数类型。此类型参数是逆变。即可以使用指定的类型或派生程度更低的类型。有关协变和逆变的更多信息,请参见泛型中的协变和逆变。
没有返回值,0-16个传入参数
3、public delegate TResult Func<out TResult>();
// // 摘要: // 封装一个不具有参数但却返回 TResult 参数指定的类型值的方法。 // // 类型参数: // TResult: // 此委托封装的方法的返回值类型。此类型参数是协变。即可以使用指定的类型或派生程度更高的类型。有关协变和逆变的更多信息,请参见泛型中的协变和逆变。 // // 返回结果: // 此委托封装的方法的返回值。
最后一个参数为传出参数,一定有传出参数。前面0-16个传入参数。
#region 泛型委托 Student stupre = new Student() { ID = 10, Name = "WYX" }; Predicate<int> pStu = new Predicate<int>(stupre.CheUsreID); Console.WriteLine(pStu(stupre.ID)); //0-16个参数 17中方法重载 Action ac1 = new Action(stupre.GetUserNameNoP); ac1(); Action<string> ac2 = new Action<string>(stupre.GetUserNameNoP); ac2("123"); //最后一个参数为返回参数out 协变 ,前面可以放0-16个参数 Func<string> fun1 = new Func<string>(stupre.ToString); fun1(); Func<int, int> fun2 = new Func<int, int>(stupre.GetUserId3); Console.WriteLine(fun2(5)); #endregion
//自定义泛型委托 NoInOutDele<int> custom1 = new NoInOutDele<int>(stupre.GetUserNameP); Console.WriteLine(custom1()); OnePDele<string> custom2 = new OnePDele<string>(stupre.GetUserNameP); Console.WriteLine(custom2("藏锋")); OutPDele<string, string, string> custom3 = new OutPDele<string, string, string>(stupre.GetUserNameNoP); Console.WriteLine(custom3("123", "456"));
匿名方法
delegate(){}
#region 匿名方法 //委托里面添加的是一个个方法,可以写一个匿名方法给他 delegate(){} //好处 可以访问上下文变量 OutPDele<string, string, string> unNameFun1 = new OutPDele<string, string, string>(delegate (string s1, string s2) { return s1 + s2; }); Console.WriteLine(unNameFun1("藏", "锋")); #endregion
语法糖,编译器会帮我们声明一个方法。(CLS中规定的是只有方法和字段)
好处:可以访问上下文变量。
什么代码可读性好,我感觉根据个人情况吧。编译到中间语言都一个样。
注意:匿名方法不能复制给变量 var g = delegate (string s) { Console.WriteLine(s); }
Lambda表达式
匿名方法的更加简单写法。把匿名方法的delegate换成=>。编译器也会把他生成一个方法。
无论是匿名方法还是Lambda表达式都是依赖于委托存在的。只有委托才调用他们。
定义:表达者委托或表达式树的匿名方法。 本质是一个匿名方法,有时候这种代码看不懂得时候就想想他的本质,委托调用他们。
#region Lambda OutPDele<string, string, string> lamdFun = new OutPDele<string, string, string>((string s1, string s2) => { return s1 + s2; }); //=>读 gos to //一个参数可以(string s)=>s或者s=>s //多个参数(string s1,string s2.....) 一定加括号 //返回只有一条语句返回 s=>{return s;} 或者s=>s (默认为返回) //多条语句 s=>{吧啦吧啦} OnePDele<string> lamdFun1 = new OnePDele<string>(s => { return s; }); #endregion
//表达是一个委托 OutPDele<string, string, string> lamdFunD = (string s1, string s2) => s1 + s2; lamdFunD.Invoke("1", "2");
Lambda表达式是一个匿名方法。语法:形参列表=>(goes to)方法体。“=>”运算符具有与“=”相同的优先级,并且是右结合性运算符。
一个参数可以(string s)=>s或者s=>s
多个参数(string s1,string s2.....) 一定加括号
返回只有一条语句返回 s=>{return s;} 或者s=>s (默认为返回)。去掉了大括号
只有一条逻辑语句的例如 s=>{Console.WriteLine("1");} 或者s=>Console.WriteLine("1"); 去掉了大括号
多条语句 s=>{吧啦吧啦}
Lambda表达式规则有三个
1、 Lambda包含的参数数量必须与委托类型包含的参数数量相同。
2、每个输入参数必须都能够隐式转换为其对应的委托参数。(逆变)
3、返回值(如果有)必须能够够转换为委托的返回类型。(协变)
扩展方法
.net framork3.0
目的:给一个类型增加行为
结构:静态类(不能嵌套,泛型),静态方法,方法第一个参数为 (this 扩展的类型 调用这个扩展方法的对象 ),后面参数为扩展方法的参数。(this string var, string s1) (this string var, string s1, string s2)
使用:在使用的类中添加他的命名空间,优先调用实例方法。
#region 扩展方法 string s = "藏锋"; Console.WriteLine(s.MargTwoStr("1", "2")); Console.WriteLine(s.MarMySelf("23")); #endregion
注意:
Console.WriteLine(s.MarMySelf("23"));
//上面和下面编译玩之后都一样的。只是上面的写的更友好。
Console.WriteLine(StrExc.MarMySelf("1", "2"));
THIS:四种用法
1,当前类的实例
public string GetUserNameNoP(string p_Name, string p_Name1) { return p_Name1 + this.Name + p_Name; }
2,串联构造函数
调用一个构造函数的时候,他再去调用另外 一个构造函数this(若干构造函数参数)
如果要构造基类的,就是base(若干构造函数参数)
public Teacher() { } public Teacher(string p_TeacherName):this() { } public Teacher(string p_TeacherName, DateTime p_CreateDate) : base(p_CreateDate) { }
3,当前模块提到的扩展方法。
4,索引器
public class MyIndex { private string[] strList = new string[10]; public string this[int index] { get { return strList[index]; } set { strList[index] = value; } } } public class MyIndexStr { //用string索引查看的药用hashtable key value列表 private Hashtable hash = new Hashtable(); public string this[string indexstr] { set { hash.Add(indexstr, value); } get { return hash[indexstr].ToString(); } } }
讲一个面试经历:有一个公司在咖啡馆面试的(其实是一个小公司,没有办公地点),所有开发语言都招,去了先根据业务设计一个算法,在填写的英文简历,通过后才面谈,刚开始讲一大推讽刺的话,在讲来我们这里可以学到好多东西,(深深的套路呀)。然后开始问专业问题,中括号怎么用的,我说 索引器,他愣了一下(后来我猜他问的是JQ里面[]属性选择器,他做前端的,可能不懂后台),后面就开始嘲讽我,然后我怼了他两句走了。见到这种人不要怂,他们也是能忽悠到一个算一个。
还有一次,他们公司开发三个人,每天加班到八点,周六也加班,然后说实行末位淘汰制,我想三个人你还淘汰啥。
迭代器
foreach的时候就是进行迭代操作,但是foreach遍历的集合必须继承IEnumerable接口实现了GetEnumerator方法(IEnumerable只有GetEnumerator()一个方法)。
foreach数组的时候,其实编译器把foreach变成for了。
迭代器的优点:对于一个较大的集合,不用一次性把数据全部加在出来。不需要遍历的时候一次加在一个。
yield:在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。(迭代器中用的就是这个东西,一次返回一个值)
#region 迭代器 都要继承IEnumerable接口实现了GetEnumerator方法。 List<Teacher> teas = new List<Teacher>(); for (int i = 0; i < 10; i++) { Teacher tae = new Teacher() { CreateUserID = i, TeacherName = i.ToString(), CreateDate = DateTime.Now }; teas.Add(tae); } //我们foreach的时候就是使用的迭代器 //但是数组也可以使用foreach,那是因为编译器把他转化为了for。中间语言中还是for //IEnumerable接口中只有一个方法。 foreach (var item in teas) { Console.WriteLine(item.TeacherName); } foreach (var item in GetIterator()) { Console.WriteLine(item.ToString()); } //foreach遍历的集合每次只会返回一个,不把所有的都加载。GetIterator()方法一段一段的执行,遍历中间结束,后面的代码也不会执行 foreach (var item in GetIterator()) { if(item==2) { break; } } #endregion
//yield :在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。 static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); //终止迭代 //yield break; yield return 2; Console.WriteLine("迭代器返回了3"); yield return 3; }
注意事项:
1:做foreach循环时多考虑线程安全性,在foreach时不要试图对被遍历的集合进行remove和add等操作任何集合,即使被标记为线程安全的,在foreach的时候,增加项和移除项的操作都会导致异常
2:IEnumerable接口是LINQ特性的核心接口,只有实现了IEnumerable接口的集合,才能执行相关的LINQ操作,比如select,where等这些操作。
LINQ
前面都是准备知识,现在进入正题。
分为两种模式
1,查询操作符(扩展方法+lambda),扩展方法 扩展的事IEnumerable<T>接口。所以linq的基础都是集合要继承IEnumerable<T>。
过滤:where,Find,FindAll,FindLast,First<T>,FirstOrDefault<>
统计函数:Count,MIn,Sum,Max
排序:OrderBy,OrderByDescending
跳过前面多少条数据取余下的数据:SKIP
从开始起获取指定数量的数据:TAKE
模糊匹配:Contains
分组:GroupBy
连接查询:Join
投影:select(select t或者select(t=>new{t.1,t.2}))
2,查询表达式
From [type] id in source
[join [type] id in source on expr equals expr [into subGroup]]
[from [type] id in source|let id=expr | where condition]
[orderby ordering,ordering,ordering,ordering...]
select select expr | group expr by key
[into id query]
tips:type是可选的,id是数据源集合中的一项,source是数据源集合, 其实是在一直循环source,每次循环把值放入select后面
expr表示一个表达式,subGroup是一个临时变量,继承自IGroup,代表一个分组。
可以有多个form,多个where,set指定临时变量
可以有0-多个排序 orderby a descing orderby b。 orderby a descing ,b,c
select new投影(匿名类,返回的类型用var 因为他生成的时候一后台定义的类型集合),group 类型 by 具体属性。标准linq 前面一般写一个var(不确定类型)。 一个查询表达式必须以select或者group by结束。select后跟要检索的内容。group by 是对检索的内容进行分组
group by 他后面只能跟 into is as 关键字,常用into 关键字放入临时数据源 然后在select 投影。
into放入临时数据源
Let 设置变量
注意:这种查询表达式会编译成上面的扩展方法+lambad。这种是一个语法糖。
推荐文章:http://www.cnblogs.com/liulun/archive/2013/02/26/2909985.html
linqpad工具:http://www.linqpad.net/Download.aspx (EF也可以用)(但是linq to sql,EF中都不建议使用linq,特别是复杂的查询。linqtosql貌似已经死了)