任务1:字符串和正则表达式 任务1-1&1-2:字符串类string
System.String类(string为别名)
注:string创建的字符串是不可变的,一旦进行了初始化,就不能改变其内容了
string的声明:string s = "...."; 字符串长度:int length = s.Length; 比较string大小:直接使用 == 即可:(s == "xxx") 字符串的连接:直接使用 + 即可:s = "..." + s; 这里s的修改不是直接修改其内容,而是重新赋值,耗费性能 -- 新建字符串用来存储连接后的字符串,并使引用s指向这个新字符串,旧的进入GC 字符串中按索引取得字符:s[index]; 遍历字符串:for循环
常用方法:
CompareTo() -- 比较字符串内容,各个字符依次比较 若两个字符串相等则返回0,若当前字符串在字母表排序比较靠前则返回-1,否则返回1 s.CompareTo(s1); // 返回值为int,0表示相等,-1表示s靠前,1表示s靠后 应用:如人名的排序等
Replace() -- 用另一个字符或者字符串替换字符串中给定的字符或者字符串 string newS = s.Replace('a', 'b'); // replace 'a' with 'b' string newS = s.Replace("a", "bbbb");
Split() -- 在出现给定字符的地方,把字符串拆分称一个字符串数组 string[] strArray = s.Split(','); // 通过','将字符串分割
SubString() -- 在字符串中检索给定位置的子字符串 "www.devsiki.com".SubString(4, 7); // 从index=4之后的位置开始截取,长度为7的子字符串,devsiki 若不给定长度,则会一直取到原字符串结尾
ToLower()/ ToUpper() 把字符串转换成小写/大写形式
Trim() -- 删除首尾的空白 string newS = " adfa ad a ".Trim(); // "adfa ad a" 应用:如玩家在输入用户名的时候,首尾是不能有空格的 对于中间的空格而言,可以使用Replace(" ", "");
IndexOf() -- 取得字符串第一次出现某个给定字符串或者字符的位置 int index = s.IndexOf("dev"); // 返回第一个字符对应的index值,若不存在,则返回-1
Concat() -- 合并字符串
CopyTo() -- 把字符串中指定的字符复制到一个数组中
Format() -- 格式化字符串
Insert() -- 把一个字符串实例插入到另一个字符串实例的制定索引处
Join() -- 合并字符串数组,创建一个新字符串
任务1-3&1-4&1-5:StringBuilder类
Sysytem.Text.StringBuilder
StringBuilder类比string的效率更高: 比如StringBuilder.Append("xxx"); 和string之间的+号连接操作的比较: string: 每次修改都需要开辟另外的存储空间 StringBuilder: 若当前空间足够,直接修改即可
创建: StringBuilder sb = new StringBuilder("..."); -- 将一个string传递给构造函数 StringBuilder sb = new StringBuilder(20); -- 初始长度 StringBuilder sb = new StringBuilder("...", 100); -- 字符数量小于100时就不需申请内存 如果超过100个字符时,会重新申请一个200(2倍)的内存区域并赋值,删除原来的 一般来说,预估sb可能的大小,在进行初始化的时候申请该大小的内存区域
方法:
Append(string) -- 在字符串末尾追加一个字符串 sb.Append("xxx"); -- s = s + "xxx";
Insert(index, string) -- 从特定index开始插入字符串 sb.Insert(0, "http://");
Remove(startIndex, length) -- 从当前字符串中删除指定长度的字符串 sb.Remove(0, 3); // 删除前三个字符
Replace() -- 用某字符/字符串替换另一个字符/字符串 sb.Replace(".", ""); 注意:sb.Replace('.', ''); 是不行的,不能替换成空字符,但是可以替换成空字符串
ToString() -- 将stringBuilder中存储的字符串,提取成一个(不可变的)string
任务1-6:VS插件Resharper
安装:VS中,工具 -> 扩展和更新 -> 联机 -> 搜索resharper -> 选择ReSharper(图标c#)
任务1-7:正则表达式及其方法
正则表达式 Regular Expression: 表述一个字符串的书写规则
用途: 1. 检索:通过正则表达式,从字符串中获取我们想要的部分 2. 匹配:判断给定的字符串是否符合正则表达式的过滤逻辑 等等:验证、提取、分割、替换等
正则表达式由元字符(普通字符和特殊字符)组成 (元字符在下一任务中详述)
常用的判断正则表达式的c#方法:
System.Text.RegularExpressions下的Regex类
IsMatch() -- 返回bool判断字符串是否匹配正则表达式 bool IsMatch(string input, string pattern); 参数: input: 要搜索匹配项的字符串。 pattern: 要匹配的正则表达式模式。 返回结果: 如果正则表达式找到匹配项,则为 true;否则,为 false。 bool IsMatch(string input, string pattern, RegexOptions options); options: 枚举值的一个按位组合,这些枚举值提供匹配选项。 返回结果: 如果正则表达式找到匹配项,则为 true;否则,为 false。 bool IsMatch(input, pattern, RegexOptions options, TimeSpan matchTimeout); matchTimeout: 超时间隔,或System.Text.RegularExpressions. Regex.InfiniteMatchTimeout指示该方法不应超时。 返回结果: 如果正则表达式找到匹配项,则为 true;否则,为 false。
Match() -- 在字符串中搜索指定的正则表达式的第一个匹配项。 返回一个包含有关匹配的信息的对象。 Match Match(string input, string pattern); Match Match(string input, string pattern, RegexOptions options); Match Match(input, pattern, RegexOptions options, TimeSpan matchTimeout);
Matches() -- 与Match()不同的是,返回的是MatchCollection,包含所有的匹配项 重载方法的参数与Match()完全相同
Replace() -- 将匹配正则表达式的所有地方用新的字符串替换 Replace(string input, string pattern, string replacement) input是源字符串,pattern是匹配的条件,replacement是替换的内容, 就是把符合匹配条件pattern的内容转换成replacement 如:string result = Regex.Replace("abc", "ab", "##"); //结果是##c,就是把字符串abc中的ab替换成## Replace(input, pattern, replacement, RegexOptions options) RegexOptions是一个枚举类型,用来做一些设定. // 比如:如果在匹配时忽略大小写就可以用RegexOptions.IgnoreCase Replace(input, pattern, MatchEvaluator evaluator); evaluator是一个代理,简单而言是一个函数指针,把一个函数做为参数参进来 由于C#里没有指针就用代理来实现类似的功能。 可以用代理绑定的函数来指定你要实现的复杂替换. Replace(input, pattern, MatchEvaluator evaluator, RegexOptions options);
关于Regex.Options:
Split() -- 在正则表达式匹配的位置,将文本拆分为一个字符串数组,并返回 string[] Split(string input, string pattern); string[] Split(string input, string pattern, RegexOptions options); string[] Split(input, pattern, RegexOptions options, TimeSpan matchTimeout);
@符号:避免编译器去解析字符串中的转义字符,而作为正则表达式的语法(元字符)存在 如:string s = @"www.baidu.com\nlkjsdflkj"; -- 这里的\n就是\n,没有其他意义
任务1-8~1-13:特殊元字符 (定位元字符、基本语法元字符、反义字符、重复描述字符、择一匹配符、分组操作符)
定位元字符:
字符 说明 \b 匹配单词的开始或结束 \B 匹配非单词的开始或结束 ^ 匹配必须出现在字符串的开头或行的开头 string str = "I am Blue cat"; Console.WriteLine(Regex.Replace(str, "^","开始:")); // "开始:I am Blue cat" $ 匹配必须出现在以下位置:字符串结尾、字符串结尾处的 \n 之前或行的结尾。 \A 指定匹配必须出现在字符串的开头(忽略 Multiline 选项)。 \z 指定匹配必须出现在字符串的结尾(忽略 Multiline 选项)。 \z 指定匹配必须出现在字符串的结尾或字符串结尾处的 \n 之前 (忽略 Multiline 选项) \G 指定匹配必须出现在上一个匹配结束的地方。 与 Match.NextMatch() 一起使用时,此断言确保所有匹配都是连续的。
基本语法元字符:
字符 说明 . 匹配除换行符以外的任意字符 \w 匹配字母、数字、下划线、汉字 (指大小写字母、0-9的数字、下划线_) \W \w的补集 ( 除“大小写字母、0-9的数字、下划线_”之外) \s 匹配任意空白符 (包括换行符/n、回车符/r、制表符/t、垂直制表符/v、换页符/f) \S \s的补集 (除\s定义的字符之外) \d 匹配数字 (0-9数字) \D 表示\d的补集 (除0-9数字之外)
实例: string input = Console.ReadLine(); string pattern = @"^\d*$"; // 正则表达式: 全数字--因为\d不是转义字符,需要@ if(Regex.IsMatch(input, pattern)) { Console.WriteLine("Valid"); }
反义字符:
字符 说明 \W \S \D \B 匹配不是单词开头或结束的位置 [^x] 匹配除了x以外的任意字符 [^adwz] 匹配除了adwz这几个字符以外的任意字符
中括号: [ab] 匹配中括号中的字符 [a-c] a字符到c字符之间的字符 (a/b/c)
实例:查找除了ahou以外的所有字符 string strFind1 = "I am a Cat!", strFind2 = "My Name's Blue cat!"; Regex.Replace(strFind1/2, @"[^ahou]","*")); // strFind1->**a**a**a* // strFind2->****a*******u***a**
重复描述字符:
字符 说明 {n} 匹配前面的字符 =n次 {n,} 匹配前面的字符 >=n次 {n,m} 匹配前面的字符 n~m次 ? 重复零次或一次 =0/1 + 重复一次或更多次 >=1 * 重复零次或更多次 >=0
实例:校验输入内容是否为合法QQ号(备注:QQ号为5-12位数字) string regexQq = @"^\d{5,12}$";
择一匹配符:
字符 说明 | 将两个匹配条件进行逻辑“或”(Or)运算。
实例: 1. 查找数字或字母 string str = "ad(d3)-df"; string regexPattern = @"[a-z]|\d"; MatchCollection newStr = Regex.Matches(str, regexPattern); 可用foreach(Match char in newStr) 来遍历得到的结果 2. 示例二:将人名输出("zhangsan;lisi,wangwu.zhaoliu") string strSplit = "zhangsan;lisi,wangwu.zhaoliu"; string regexSplitstr = @"[;] | [,] | [.]"; // 使用string regexSplitstr = @"[;,.]"; 也可以 string[] resArray = Regex.Split(strSplit, regexSplitstr); 分组操作符:
用小括号来指定子表达式(也叫做分组)
实例:校验IP4地址 (如: 192.168.1.4, 为四段, 每段最多三位, 每段为0~255,且第一位不为0) string regex = @"^(((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?))$"; Regex.IsMatch(inputStrIp4, regexStrIp4));
任务2:委托、Lambda表达式和事件 任务2-1&2-2:什么是委托
如果要把方法当做参数来传递的时候,就要通过委托 简单而言:委托是一个类型,可以赋值一个方法的引用给该类型 -- 之前的变量都是赋值数据的
委托的声明: 使用一个类的两个阶段: 定义类:告诉编译器这个类由什么字段和方法组成 定义后可以使用对象:用这个类去实例化对象
使用一个委托的两个阶段: 定义委托:告诉编译器这个委托可以指向哪些类型的方法 创建该委托的实例:
定义委托的语法: delegate void IntMethodInvoker(int x); 定义了名为IntMethodInvoker的委托,指向的方法带有一个int参数,方法返回void
创建委托的实例/ 使用委托:
第一种创建委托的方式:构造函数 第一种使用委托的方式:通过委托名
private delegate string GetAString(); // 定义委托,指向的方法没有参数,返回string
static void Main() {
int x = 40;
// 创建一个委托实例,指向对象x的ToString()方法,没有写()
// 注:此时没有调用该方法,只是将委托stringMethod指向了x的ToString()方法
GetAString stringMethod = new GetAString(x.ToString);
// 通过委托实例stringMethod来调用指向的方法
Console.WriteLine(stringMethod());
// 通过委托调用的方法,和直接调用方法的结果是一样的
}
第二种创建委托的方式:直接通过函数名 GetAString stringMethod = x.ToString; 第二种使用委托的方式:通过Invoke方法从而调用委托指向的方法 stringMethod.Invoke();
把委托类型的实例当做参数来使用:
private delegate void PrintString();
PrintString method = Method1;
PrintStr(method);
method = Method2;
PrintStr(method); // 输出 "Method1\nMethod2"
static void PrintStr(PrintString print) {
print(); // 将PrintString类型的委托作为参数传入PrintStr方法
}
static void Method1() {
Console.WriteLine("Method1");
}
static void Method2() {
Console.WriteLine("Method2");
}
任务2-3:Action委托
除了上一节提到的自定义的委托类型,系统内置/预定义的委托类型:Action和Func
Action委托指向的是返回值为void,且没有参数的方法 Action a = MethodName;
Action委托的扩展<泛型>:可以指向返回值是void,而且有参数的方法 (最大支持16个参数) -- 参数列表必须和指向的方法的参数列表顺序对应 Action<int> a = MethodName; // 有一个int参数 Action<int, bool> a = MethodName; // 有一个int和一个bool参数 -- 指向重载方法时,系统会自动匹配参数合适的方法
任务2-4:Func委托
Func委托指向的是有返回值,且没有参数的方法 Func<int> f = MethodName; // 必须有一个泛型,表示返回值类型
Func委托的扩展:可以指向有返回值,且有参数的方法 (最大支持16个参数) Func<string, int> f = MethodName; // 参数为string, 返回值为int -- 最后一个是返回类型,其他的都是参数类型
任务2-5&2-6:通用类型的方法 -- 实例:冒泡排序
冒泡排序:从小到大 第一轮开始: 0号与1号比较,若0号大,则0号和1号交换位置; 1号与2号比较,若1号大,则1号和2号交换位置; 以此类推,一轮过后,最大的数字被交换到数组的最后; 第二轮开始: 0号与1号比,一直比到n-2号与n-1号比 一轮过后,次大的数字被交换到数组的n-1处 n轮过后,数组有序
改进: 当进行一轮比较之后,没有数字发生位置交换,则判定已经为有序数组,终止循环
int类型的冒泡排序:
bool swapped = true;
do{
swapped = false;
for(int i =0;i<sortArray.Length -1;i++){
if(sortArray[i]>sortArray[i+1]){
int temp= sortArray[i];
sortArray[i]=sortArray[i+1];
sortArray[i+1]=temp;
swapped = true;
}
}
}while(swapped);
扩展-->通用的冒泡排序:通过泛型+委托的方式实现
实例:对雇员类Employee,按照Salary进行排序
class Employee{
public Employ(string name,decimal salary){
this.Name = name;
this.Salary = salary;
}
public string Name{get;private set;}
public decimal Salary{get;private set;}
public static bool CompareSalary(Employee e1,Employee e2){
return e1.salary>e2.salary;
}
}
通过泛型定义排序方法: // 1. 通过委托的方式将比较函数传递过来 // 2. 因为不同类的比较方法是不同的,故针对每个类写出对应的比较大小方法即可 见Employee.CompareSalary () // 3. 调用该方法的时候通过comparison委托将对应类的比较方法传入即可 public static void Sort<T> (List<T> sortArray, Func<T, T, bool> comparison)
public static void Sort<T>(T[] sortArray,Func<T,T,bool> comparision ){
bool swapped = true;
do{
swapped = false;
for(int i=0;i<sortArray.Count-1;i++){
if(comparision(sortArray[i+1],sortArray[i])){
T temp = sortArray[i];
sortArray[i]=sortArray[i+1];
sortArray[i+1]=temp;
swapped = true;
}
}
}while(swapped);
}
使用:
static void Main(){
Employee[] employees = {
new Employee("Bunny",20000),
new Employee("Bunny",10000),
new Employee("Bunny",25000),
};
Sort<Employee>(employees, Employee.CompareSalary);
}
任务2-7:多播委托
多播委托:指向了多个方法的委托
用处:使用多播委托,可以按照顺序调用多个方法,但是只能得到最后一个方法的结果
一般把多播委托的返回类型声明为void
多播委托包含一个逐个调用的委托集合,若其中一个抛出异常,整个迭代就会停止
如何进行多播委托: Action action = Test1; action += Test2; // 添加一个委托的引用给action // 此时action() 会顺序执行Test1()和Test2() action -= Test1; // 删除引用,可以直接删除 action -= Test2; // 报错 -- 当一个委托没有指向任何方法时,会出现空指针异常
如何取得多播委托中所有的委托: Delegate[] delegates = action.GetInvocationList(); foreach(Delegate d in delegates) { d.DynamicInvoke(null); // 单独调用 -- 如果需要参数,则需传递 }
任务2-8:匿名方法
之前使用委托都需要先定义一个方法,然后将方法指定给委托的实例。
匿名方法: 定义一个没有方法名的方法 -- 本质就是方法,只不过没有定义名字 用delegate关键字 不用声明返回类型,直接在方法中返回即可
Func<int,int,int> plus = delegate (int a,int b){
return a + b;
};
int res = plus(34,34);
Console.WriteLine(res);
匿名方法无法直接调用,只能通过委托进行调用
一般来说,匿名方法用于进行回调
任务2-9:Lambda表达式
Lambda表达式:匿名方法的简写形式
Lambda表达式的书写规则: 1. 不用写delegate 2. 不用写参数类型,因为在委托中已经定义了参数类型 3. 使用 => 表示这是一个Lambda表达式
实例:
Func<int,int,int> plus = delegate (int a,int b){
return a + b;
};
--> Lambda
Func<int,int,int> plus = (a,b)=> {
return a + b;
};
=>的左边列出了参数(只有一个参数的时候可以不写括号); =>的右边为匿名方法的方法体(只有一个语句时可以不写大括号; 且需要return的时候可以不写return关键字) 如:Func<int int> f = a => a+1; // 因为有返回值int,所以传入参数a,返回a+1
Lambda表达式外部的变量: 通过Lambda表达式可以访问外部的变量 如:int somVal = 5; Func<int, int> f = x => x + somVal; 这个时候,如果没有正确使用,会变得非常危险: 因为一个方法的使用一般是通过传递的参数决定的 而由于可以访问外部变量,导致执行也会被外部变量的变化而影响,结果不可控
任务2-10:事件
之前学了委托: 实例:
class Program {
public delegate void MyDelegate(); // 定义委托类型
public MyDelegate myDelegate; // 声明委托变量,作为成员变量
static void Main(string[] args) {
Program program;
program.myDelegate = Test(); // 委托指向方法
myDelegate(); // 调用委托指向的方法
}
static void Test() {
...
}
}
事件(event):具有特殊签名的委托,是类/对象向其他类/对象通知发生的事情的一种委托 事件基于委托,为委托提供了一个发布/订阅机制
实例 -- 在上例中修改 在声明委托变量的时候,加上event关键字 public event MyDelegate myDelegate; -- 这就是一个事件了
事件的性质: 1. 委托可以声明成一个局部变量,但是事件只能作为一个类的成员变量 2. 事件的命名一般为NameEvent 3. 事件的返回值是一个委托类型
任务2-11&2-12:观察者设计模式 && 委托和事件的区别
观察者设计模式: 有观察者和被观察者,当被观察者做出某个操作时,触发事件,所有观察者做出对应操作
例: Unity中,点击开始按钮 (被观察者),很多方法如加载场景打开音乐 (观察者)就随之调用
实例:猫与老鼠 有三只动物,猫(名叫Tom),还有两只老鼠(Jerry和Jack),当猫叫的时候,触发事件(CatShout),然后两只老鼠开始逃跑(MouseRun)
猫类:
class Cat {
private string name;
public Cat(string name) {
this.name = name;
}
public void CatShout() {
// 被观察者的状态改变
Console.WriteLine(name + " mew);
}
}
鼠类:
class Mouse {
private string name;
public Mouse(string name) {
this.name = name;
}
public void RunAway() {
Console.WriteLine(name + " run away");
}
}
Main():
class Program {
public static void Main(string[] args) {
Cat cat = new Cat("Tom");
Mouse mouse1 = new Mouse("Jerry");
Mouse mouse2 = new Mouse("Jack");
cat.CatShout();
}
}
这个时候Cat.CatShout()并不能将消息广播给Mouse 需要手动修改,在该函数中传递两个Mouse,并调用mouse.RunAway() -- 无扩展性,若添加了其他老鼠,需要修改Cat的代码 -- 耦合性高
解决方法:优化1 -- 通过委托 在Cat中定义一个委托,让观察者将自己的对应操作添加到委托中 在CatShout()中调用这个委托即可
在Cat中:
public Action CatShouting;
// 在CatShout()中调用这个委托
public void CatShout() {
if(CatShouting!=null) { // 安全判断
CatShouting();
}
}
在Main中: cat.CatShouting += mouse1.RunAway; // 进行注册
优点: 如果有新的老鼠,直接在Main中注册即可,不需要改写Cat类
缺点: 每次新建观察者时,都需要进行注册 因为每一个观察者都需要进行注册
解决方法:优化2 -- 将猫的对象传给Mouse的构造函数,在构造函数中进行注册
在Mouse的构造函数中传入一个猫的对象 public Mouse(string name, Cat cat) { cat.CatShouting += this.RunAway;
缺点:但是委托CatShouting在外界可以直接被调用 如在Main中调用 cat.CatShouting(); 这就比较危险了 因为Cat自身的状态改变cat自己知道就好,不应该通过外界调用
解决方法:优化3 -- 通过事件
在Action的声明中加上event: public event Action CatShoutingEvent; -- 命名规则+event
此时,这个事件就不能在外部通过类的对象进行调用了,只能在类内部进行调用 但是依然可以在外部进行注册
这里的CatShoutingEvent事件可以看做是在发布一个消息 Mouse()中的cat.CatShoutingEvent += this.RunAway; 可以看做是订阅了这个消息 -- 事件的发布/订阅机制
事件与委托的联系和区别:
1. 事件是一种特殊的受限制的委托,是委托的一种特殊应用 (不能在外部被调用的委托)
2. 常使用委托来进行回调 比如做一个动画,动画完成后进行操作: 因为动画需要时间来完成,因此传递给它一个委托,动画执行完后调用委托 这个委托指向的方法就叫回调函数
常使用事件来进行外发接口,给其他类进行注册
任务3:LINQ -- 数据查询 任务3-1&3-2:LINQ的基础使用(表达式) && 扩展写法(方法)
实例:武林高手数据查询
创建武林高手类 MartialArtMaster
class MartialArtMaster {
public int Id {get; set;}
public string Name {get; set; }
public int age {get; set; }
public string Menpai {get; set; }
public string Kongfu {get; set; }
public int level {get; set; }
}
创建武学类 Kongfu
class Kongfu {
public int Id {get; set; }
public string Name {get; set; }
public int Lethality {get; set; } // 杀伤力
}
联系:要想知道武林高手的某个武学的杀伤力,需要通过两个类才能得知
在Main中初始化武林高手和武学
创建完数据,尝试使用LINQ进行数据查询
1. 查询武林高手中所有级别 level>8的:
普通方法:通过foreach遍历查找,将查找到的符合的武林高手存入res数组即可
LINQ方法 -- 表达式写法:
var res = from m in masterList // 设置查询集合:在masterList列表中,m指代每个对象
where m.level > 8 // where 查询条件
select m; // 把符合要求的m的集合返回
foreach(var element in res) {
... 输出
}
若select语句为 select m.Name; 则返回的为m的Name属性 -- string[]
LINQ方法 -- 方法写法:
var res = masterList.Where(FilterMethod);
// FilterMethod是一个委托,方法需要满足 bool MethodName(MartialArtMaster m);
// 会遍历masterList中的所有元素,并作为参数传入FilterMethod
// 如果返回值为false,则被过滤掉
static bool FilterMethod(MartialArtMaster m) {
return m.level > 8;
}
一般情况会将委托写成Lambda表达式的形式:
var res = masterList.Where( master => master.Level > 8);
2. 假设限制条件有多个
LINQ表达式: var res = from m in masterList where m.Level > 8 && m.Menpai == "丐帮" select m.Name;
LINQ方法 + Lambda表达式: var res = masterList.Where(m => m.Level > 8 && m.Menpai = "丐帮");
LINQ方法也一样。
任务3-3&3-4:LINQ集合联合查询
LINQ联合查询: 联合两个列表进行查询 -- 两个列表进行联合查询,结果是n*m条记录 第一个列表的任意一条记录,会跟第二个列表的所有记录进行组合生成新的m个记录
var res = from m in masterList from k in kongfuList select new {master = m, kongfu = k}; // new 新建临时对象,一共两个字段,master和kongfu 得到的结果为66条记录
但是有55条记录是没用的,这两个列表是有关联的 -- Kongfu的值
var res = from m in masterList from k in KongfuList where m.Kongfu == k.Name selece new {master = m, kongfu = k} ; 这时,返回的就只有11条记录了
实例:要取得技能杀伤力>90的武林高手名字
var res = from m in masterList from k in kongfuList where m.Kongfu == k.Name && k.Power > 90 select m.Name;
联合查询的扩展方法的写法:
masterList.SelectMany()
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector);
参数:两个Func委托; 第一个Func:参数为第一个列表的元素,返回值为第二个列表 用途:将第二个列表加入第一个列表,做联合查询 m => kongfuList -- m不做任何操作(但也要传入),返回值为kongfuList m指的是masterList中的元素 第二个Func:参数为两个列表的元素,返回值为需要返回的元素 用途:进行联合查询 (m, k) => new {master = m, kongfu = k} k指的是kongfuList中的元素 var res = masterList.SelectMany( m=>kongfuList, (m,k)=>new {master=m, kongfu=k}); // res存放的为66条联合查询的记录
由于上面LINQ扩展方法返回的是结果列表 可以直接进行Where()操作 .Where(x => x.master.Kongfu == x.kongfu.Name); x指的是前面的SelectMany返回的66条记录中的元素 // 此时,res存放的为11条过滤后的联合查询的记录 再添加条件判断Power是否大于90 .Where(x=>x.master.Kongfu == x.kongfu.Name && x.kongfu.Power > 90);
任务3-5:对结果进行排序 -- orderby
order by一般位于where关键词后
默认从小到大排序
实例: var res = from m in masterList where m.Level>8 && m.Menpai == "丐帮" orderby m.Age (descending) select m; // 如果在orderby后有descending关键字,则降序排序,否则默认为升序
按多个字段进行排序 在orderby后用逗号把字段进行分割 如:orderby m.Age, m.Level -- 按照age排序,如果age相同的,再按照level排序
扩展方法:OrderBy() / OrderByDescending()
var res = masterList.Where(m=>m.Level>8 && m.Menpai == "丐帮").OrderBy(m=>m.Age);
var res = masterList.Where(m=>m.Level>8 && m.Menpai == "丐帮").OrderBy(m=>m.Age).OrderBy(m=>m.Level); 可以用两个OrderBy方法进行多个字段排序吗? -- 不行,第二次OrderBy会把所有记录重新排序,覆盖了第一次的排序
解决方法:ThenBy()
var res = masterList.Where(m=>m.Level>8 && m.Menpai == "丐帮").OrderBy(m=>m.Age).ThenBy(m=>m.Level);
任务3-6:Join on集合联合查询
另一种联合查询:Join on
var res = from m in masterList join k in kongfuList on m.Kongfu equals k.Name select new {mastar=m, kongfu=k} ; // 将masterList和kongfuList做连接,连接条件为 m.Kongfu equals k.Name
任务3-7&3-8:对结果进行分组操作 into groups && group by
分组查询1:into groups 实例: 把武林高手按照所学功夫分类,看看那种功夫学习人数最多
1. 联合查询 -- 把masterList Join on KongfuList中 var res = from k in kongfuList join m in masterList on k.Name equals m.kongfu select new {kongfu = k, master = m}; // 得到每个功夫都有对应学习的武林高手
2. 对每种功夫进行分组 -- into groups var res = from k in kongfuList join m in masterList on k.Name equals m.kongfu into groups select new {kongfu = k, count = groups.Count() }; // into groups 表示按照k.Name或m.kongfu进行分组 // 因为已经分组了,因此无法继续输出m了 // 定义字段count,保存groups.Count()值
3. 如果想要在按照所学人数的多少进行排序 var res = from k in kongfuList join m in masterList on k.Name equals m.kongfu into groups orderby groups.Count() select new {kongfu = k, count = groups.Count() };
分组查询2:group by into
按照自身字段分组 -- 只对一个集合操作时,按照自身的字段的值进行分组
实例:将武林高手的集合按照武林门派进行分组,并返回每一门派的人数
var res = from m in masterList group m by m.Menpai into g select new {menpai = g.Key , count = g.Count()) // 将m按照m.Menpai进行分组,放到g中 // 因为已经进行了分组, 只有组的信息了,所以单个成员的属性是获取不到的 // g.Key -- 表示按照哪个属性分的组
任务3-9:量词操作符 any all
用途:判断
Any() -- 实例:判断集合当中是否有属于丐帮的人
Any() 传入一个判断是非的委托
bool res = masterList.Any( m => m.Menpai == "丐帮"); // 如果有,则返回true
All() -- 实例:判断集合当中是否都属于丐帮
All()使用方法和Any()相同
bool res = masterList.All( m => m.Menpai == "丐帮");
任务3-10:LINQ总结
一般微软的语言都支持LINQ的语法
LINQ可以支持从很多数据源进行查询 上述例子我们都是对Objects进行查询 还有xml, sql, dataset, entities等数据源 LINQ to Objects部分的命名空间是System.Linq
任务4:反射和特性 任务4-1:反射和特性 -- Type类
任务4-2:反射和特性 -- Assembly程序集类
任务4-3:Obsolete特性
任务4-4:Contional特性
任务4-5:调用者信息特性
任务4-6:DebuggerStepThrough特性
任务4-7:创建自定义特性
任务5:线程、任务和同步 任务5-1:进程和线程的概念
进程 - Process
任何时刻,单核CPU只能运行一个进程,其他进程处于等待状态 一个进程至少包含一个线程,也可以有多个线程 一般情况下一个应用程序启用一个进程
同一个进程的内存空间对于它的进程来说是共享的,每个线程都可以享用这部分内存空间 比如进程中的变量,是它的线程都可以访问的
互斥链 (Mutual Exclusion -- Mutex):防止多个线程同时读写某一块内存区域 先使用的时候加锁,后使用的人看到锁就排队,直到锁打开再进行使用
信号量 (Semaphore):保证多个线程不会互相冲突 某些内存区域只能供给固定数目的线程使用,超过的线程需要排队 这时当线程在使用时,钥匙减1,没有钥匙时就需要排队了。
Mutex可以看成是Semaphore的一个特殊情况 (n=1),但是因为Mutex简单、效率高,因此能用mutex就用mutex
使用线程的情况: 示例:在Main函数中,一段代码用于下载文件,一段代码用于移动文件 一个线程中的代码是从上到下执行的,于是需要等待文件下载完,才能执行其他代码 -- 多线程:在一个线程中执行下载文件的代码,在另一个线程中移动文件,还有Main线程执行其他代码
-- 一般会对比较耗时的操作另外开启一个线程
任务5-2:线程开启方式1 -- 异步委托
通过委托开启线程 action.BeginInvoke(); Action<int> a = Test; a.BeginInvoke(100, null, null); // 开启一个新的线程去执行a引用的方法 // BeginInvoke的最后两个参数的作用见后,其他参数作为参数传递给引用方法
如果委托引用的方法有返回值 func.BeginInvoke(); Func<int, int> f = Test; f.BeginInvoke(100, null, null); // 因为这个方法是异步执行的,因此新的线程可能需要很长的运行时间 // 当新的线程执行完的时候,才能得到返回值 // 返回值是IAsyncResult类型的,这个返回值可以取得线程的状态 IAsyncResult ar = f.BeginInvoke(100, null, null); // 用循环进行判断 while(!ar.IsCompleted) { // 新线程操作没有完成 Thread.Sleep(10); // Main线程休息10ms,用来控制检测频率,不需要一直检测 } // 新线程操作完成了,取得异步委托的返回值 int res = f.EndInvoke(ar);
任务5-3:检测委托线程的结束 -- 通过等待句柄和回调函数
上一节中使用循环来监测线程是否结束 还有两种方式可以检测委托线程的结束:等待句柄和回调函数
等待句柄:
当我们通过BeginInvoke开启一个异步委托的时候,返回的结果是IAsyncResult,我们可以通过它的AsyncWaitHandle属性访问等待句柄。这个属性返回一个WaitHandler类型的对象,它的WaitOne()方法可以等待委托线程完成其任务,WaitOne方法可以设置一个超时时间作为参数(要等待的最长时间),如果发生超时就返回false。
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000); -- 等待当前线程结束,再执行下面的代码 -- 参数表示超时时间(ms),若等待超过这个时间,会直接返回true/false来表示线程是否结束
Func<int, int> f = Test;
IAsyncResult ar = f.BeginInvoke(100, null, null);
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);
if(isEnd) {
int res = f.EndInvoke(ar);
}
异步回调方法:
BeginInvoke() 的最后两个参数,之前都是写的null 倒数第二个参数是一个满足AsyncCallback委托的方法 AsyncCallback委托定义了一个以IAsyncResult类型为参数,且返回类型是void,表示回调函数 当线程结束的时候会调用这个委托指向的方法
Func<int, int> f = Test; IAsyncResult ar = f.BeginInvoke(100, OnCallBack, null);
static void OnCallBack(IAsyncResult ar) { }
怎么取得返回值呢? -- 在回调函数里面取得
倒数第一个参数用来给回调函数传递数据 对于最后一个参数,可以传递任意对象,以便从回调方法中访问它。 (我们可以设置为委托实例,这样就可以在回调方法中获取委托方法的结果)
Func<int, int> f = Test;
IAsyncResult ar = f.BeginInvoke(100, OnCallBack, f);
static void
|
请发表评论