Siki_Unity_2-9_C#高级教程(未完)
Unity 2-9 C#高级教程
任务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 = ;
// 创建一个委托实例,指向对象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 =;i<sortArray.Length -;i++){
if(sortArray[i]>sortArray[i+]){
int temp= sortArray[i];
sortArray[i]=sortArray[i+];
sortArray[i+]=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=;i<sortArray.Count-;i++){
if(comparision(sortArray[i+],sortArray[i])){
T temp = sortArray[i];
sortArray[i]=sortArray[i+];
sortArray[i+]=temp;
swapped = true;
}
}
}while(swapped);
}
使用:
static void Main(){
Employee[] employees = {
new Employee("Bunny",),
new Employee("Bunny",),
new Employee("Bunny",),
};
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(,);
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 > // 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 > ;
}
一般情况会将委托写成Lambda表达式的形式:
var res = masterList.Where( master => master.Level > );
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(, null, null);
bool isEnd = ar.AsyncWaitHandle.WaitOne();
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(, OnCallBack, f); static void OnCallBack(IAsyncResult ar) {
Func<int, int> f = as.AsyncState as Func<int, int>;
int res = f.EndInvoke(ar);
Console.WriteLine(res);
}
将回调函数改写成Lambda表达式:-- 更加简便
Func<int, int> f = Test;
f.BeginInvoke(, ar => {
int res = f.EndInvoke(ar);
Console.WriteLine(res);
} , null);
// 因为Lambda表达式可以直接访问到外部变量
// 因此没有必要通过最后一个参数传递f进去
任务5-4:线程开启方法2 -- 通过Thread类
通过Thread开启线程 -- System.Threading;
// 创建线程,但并没有启动
Thread thread = new Thread(DownloadFile); // 传入委托给构造函数
// 启动线程
thread.Start();
获取线程ID:
在线程中,Thread.CurrentThread.ManagedThreadId.
传递参数1:-- 通过Start()传递,参数的类型必须为object类型
Thread thread = new Thread(DownloadFile); // 不变
thread.Start("...."); // 通过Start传递参数
static void DownloadFile(object filename) {...}
传递参数2:-- 通过新建类,并将线程的方法定义为类中的实例方法
新建类,将所有需要传递的参数以成员的方式存在新建类中
class MyThread {
private string filename;
private string filepath; public MyThread(string name, string path) {
filename = name;
filepath = path;
} public void DownloadFile() {
方法体 -- 可以直接访问私有数据
}
}
此时在外部将对象的普通方法作为委托传递给Thread构造函数即可(之前的是静态方法)
MyThread myThread = new MyThread("name", "path");
Thread thread = new Thread(myThread.DownloadFile);
thread.Start();
通过Lambda表达式:
Thread thread = new Thread(()=>{
方法体;
});
thread.Start();
任务5-5:线程的其他概念 -- 后台前台线程、线程的优先级、线程的状态
前台线程和后台线程:
有一个前台线程在运行的话,应用程序的进程就在运行
即如果有前台线程在运行,但是Main方法结束了,那么应用程序的进程仍然在运行,直到所有线程结束
但是如果Main方法结束了,那么后台线程会被强制结束
当所有的前台线程运行完毕,如果还有后台线程运行的话,所有的后台线程会被终止掉。
默认情况下:
使用Thread类创建的线程是前台线程
使用线程池创建的线程是后台线程
用Thread创建线程的时候,可以通过.IsBackground来设置前台/后台
Thread thread = new Thread(...);
thread.IsBackground = true; // 设置为后台线程
线程的优先级:
线程由操作系统调度,一个CPU同一时间只能运行一个线程。
当有很多线程需要CPU去执行的时候,线程调度器会根据线程的优先级去判断先去执行哪一个线程
如果优先级相同的话,就使用一个循环调度规则,逐个执行每个线程。(每个线程分配时间相同)
在Thead类中,可以设置Priority属性 (线程的基本优先级)。
Priority属性是一个ThreadPriority枚举定义的一个值。
定义的级别有Highest, AboveNormal, BelowNormal 和 Lowest。
控制线程:
1. 获取线程的状态 -- Running / Unstarted
当调用thread.Start()后,新线程处于Unstarted状态
当操作系统的线程调度器选择了要运行的线程,才会变为Running状态
使用Thread.Sleep() 可以让当前线程休眠进入WaitSleepJoin状态
2. 使用 thread.Abort() 强制停止线程
调用这个方法,会在终止要终止的线程中抛出一个ThreadAbortException类型的异常
我们可以try catch这个异常,然后在线程结束前做一些清理的工作。
3. 如果需要等待线程的结束,可以调用 thread.Join()
表示把Thread加入进来,停止当前线程,并把它设置为WaitSleepJoin状态
直到加入的线程完成为止,再继续执行下面的代码
任务5-6:线程开启方法3 -- 线程池
创建线程需要时间。
如果有不同的小任务要完成,就可以事先创建许多线程 , 在应完成这些任务时发出请求。
这个线程数最好在需要更多的线程时增加,在需要释放资源时减少。
系统自带一个ThreadPool类,用于管理线程。
这个类会在需要时增减池中线程的线程数,直到达到最大的线程数。
在双核 CPU中,默认设置为1023个工作线程和 1000个 I/o线程。
也可以手动指定在创建线程池时应该立即启动的最小线程数,以及线程池中可用的最大线程数。
如果有更多的作业要处理,且线程池中线程的个数也到了极限,
那么最新的作业就要排队,且必须等待线程完成其任务。
线程池默认创建出来的任务都是后台线程
注意:不能把线程池的线程修改为前台线程
不能修改线程池中线程的优先级和名称
线程池中的线程只能用于运行时间段的任务
实例:开启工作线程
static void ThreadMethod(object state) {
// 需要一个object类的参数
}
Main() {
ThreadPool.QueueUserWorkItem(ThreadMethod);
// 如果想要传递数据,在ThreadMethod后写上逗号和实参即可
任务5-7&5-8:线程开启方法4 -- 任务
通过任务开启线程 -- 三种方式
第一种 -- 和Thread有些类似
任务Task其实是线程的一种封装
Task t = new Task(TaskMethod); // 将委托指向的方法传递过来
t.Start();
第二种 -- 通过TaskFactory 工厂模式
TaskFactory tf = new TaskFactory();
// 通过任务工厂 TaskFactory 可以对许多task进行操作
Task t = tf.StartNew(TaskMethod);
连续任务:
如果一个任务t1的执行时依赖于另一个任务t2的,即t1需要在t2执行完成后才开始执行
-- 使用连续任务解决 -- task.ContinueWith()
实例:
Task t1 = new Task(DoFirst);
Task t2 = t1.ContinueWith(DoSecond);
Task t3 = t1.ContinueWith(DoSecond);
Task t4 = t1.ContinueWtih(DoError, TaskContinuationOptions.OnlyOnFaulted);
任务层次结构:
在一个任务中启动另一个新的任务,即新的任务为当前任务的子任务
若父任务执行完,但子任务没有执行完,则父任务状态会设置为WaitingForChildrenToComplete;当子任务也执行完了,则父任务的状态为RunToCompletion
实例:
static void Main() {
var parent = new Task(ParentTask);
partent.Start();
Thread.Sleep();
Console.WriteLine(parent.Status);
Thread.Sleep();
Console.WriteLine(parent.Status);
}
static void ParentTask() {
var child = new task(ChildTask);
child.Start();
Thread.Sleep();
}
static void ChildTask() {
Thread.Sleep();
}
任务5-9:线程中会遇到的问题 -- 争用条件和死锁
争用条件 Race Conditions:
A race condition occurs when two threads access a shared variable at the same time.
多个线程同时访问一个变量时,造成的读写冲突
实例:
线程要执行的委托指向的方法:
class MyThreadObject {
private int state = ; // shared variable public void ChangeState() {
state++;
if(state == ) { // 目前来看这个条件永远不会满足
输出state=;
}
state = ;
}
}
在Main中通过线程执行上述方法:
class Program {
... Main ... {
MyThreadObject myThread = new MyThreadObject();
Task t = new Task(ChangeState);
t.Start(myThread); // 启动线程 // 定义函数,执行多次myThread.ChangeState()
static void ChangeState(Object o) {
// 把Object类型转换成原来类型
MyThreadObject myThread = o as MyThreadObject;
while(true) {
myThread.ChangeState);
}
}
}
运行:没有任何输出 -- 因为只有一个线程时按照顺序执行,判断处并不会出现state=5的情况
修改 -- 增加一个线程,同时执行ChangeState()
增加一个 Task t = new Task(ChangeState); t.Start(myThread);
或使用TaskFactory
运行:输出大量的state=5
原因:当一个线程执行到state=5的时候,另一个线程正好进行判断condition
争用条件的解决方法 -- 对当前的shared对象加锁
锁 lock():向系统申请对指定对象加锁,锁定该对象
如果对象没有被锁定,那么可以j进行访问
如果对象被锁定了,则需要等待锁定解除后才能进行访问
实例:lock(object) {}
while(true) {
lock(myThread) { // 对myThread对象加锁
myThread.ChangeState(); // 同一时间,只能有一个线程执行这个操作
} // 解锁
}
死锁 Deadlocks:由锁产生的问题
当两个线程同时申请到了一个锁,而两个线程在解锁之前遇到了另一个锁又是对方申请到的第一
个锁,此时两个线程都在等待对方解锁第一个锁,产生了死锁
实例:
public class SampleThread{
private StateObject s1;
private StateObject s2;
public SampleThread(StateObject s1,StateObject s2){
this.s1= s1;
this.s2 = s2;
}
public void Deadlock1(){
int i =;
while(true){
lock(s1){ // 先锁s1
lock(s2){ // 后锁s2
s1.ChangeState(i);
s2.ChangeState(i);
i++;
Console.WriteLine("Running i : "+i);
}}}}}
public void Deadlock2(){
int i =;
while(true){
lock(s2){ // 先锁s2
lock(s1){ // 后锁s1
s1.ChangeState(i);
s2.ChangeState(i);
i++;
Console.WriteLine("Running i : "+i);
}}}}} Main... {
var state1 = new StateObject();
var state2 = new StateObject();
new Task(new SampleTask(s1,s2).DeadLock1).Start();
new Task(new SampleTask(s1,s2).DeadLock2).Start();
}
解决方法:
在编程的开始设计阶段,设计锁定的顺序即可
比如上例中:
两个方法都必须按照先锁定s1后锁定s2的顺序(或反过来)
任务6:网络
任务7:文件操作
任务8:xml操作、jason操作和excel操作
Siki_Unity_2-9_C#高级教程(未完)的更多相关文章
- [教程] [承風雅傳HSU]用ES4封裝Win7---ES4 Win7封裝教程(未完待續)
[教程] [承風雅傳HSU]用ES4封裝Win7---ES4 Win7封裝教程(未完待續) a10036it 发表于 2015-7-27 21:11:19 https://www.itsk.com/t ...
- Mybatis各语句高级用法(未完待续)
更多的语法请参考官网 http://www.mybatis.org/mybatis-3/dynamic-sql.html# 环境:MySQL5.6,jdk1.8 建议:所有的参数加上@Param re ...
- unix高级环境编程学习笔记第七章(未完)
博客地址:http://www.cnblogs.com/zengjianrong/p/3222081.html 7.1 引言 Main函数调用:命令行参数:存储器布局:如何分配存储器:进程使用env: ...
- JavaScript高级教程
JavaScript高级教程 基础总结深入 数据类型 分类 you are so nb! undefined :undefined string :任意字符串 sybmol: object:任意对象, ...
- ios cocopods 安装使用及高级教程
CocoaPods简介 每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如Java语言的Maven,nodejs的npm.随着iOS开发者的增多,业界也出现了为iOS程序提供依赖管理的工具,它的 ...
- 【读书笔记】.Net并行编程高级教程--Parallel
一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机 ...
- Net并行编程高级教程--Parallel
Net并行编程高级教程--Parallel 一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控 ...
- Go web编程学习笔记——未完待续
1. 1).GOPATH设置 先设置自己的GOPATH,可以在本机中运行$PATH进行查看: userdeMacBook-Pro:~ user$ $GOPATH -bash: /Users/user/ ...
- AutoMapper介绍(未完待续、部分没实现)
实体间转换工具.其实也可以用Json来实现同名属性.异名属性(用JsonProperty指明)的自动转换 最新版本6.11 需要使用vs2013以上.vs2012下载新版 nuget会遇到问题.只能旧 ...
随机推荐
- rsync 数据备份+cron+mailx案例
大家都知道数据非常重要的,需要经常备份,如果备份了,但无法恢复还原,那就证明你备份的很失败,所有当我们备份了数据需要检查是否备份完整,是否可用可恢复.以下为一个企业案例: 某公司里有一台Web服务器, ...
- 虚拟机克隆linux centos 6.5 系统网卡配置
作为一个刚刚接触linux系统的小白来说,VMware虚拟机安装好CentOS6.5系统后,纯净的系统多克隆几份出来方便后期做试验.克隆步骤很简单,克隆后出现的问题是克隆后的网卡MAC地址和原系统MA ...
- python基础学习15----异常处理
异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件). 1.异常的类型 异常的类型多种多样,常见的异常有: AttributeE ...
- AWS服务学习
什么是云计算? 云计算是用户通过Internet云服务平台按需提供计算能力.数据库存储.应用程序和其他IT资源,采用按需支付定价模式 无论您是在运行拥有数百万移动用户的照片共享应用程序,还是要为您的业 ...
- hadoop集群为分布式搭建
1.准备Linux环境设置虚拟机网络 1.0点击VMware快捷方式,右键打开文件所在位置 -> 双击vmnetcfg.exe -> VMnet1 host-only ->修改 ...
- mapreduce设置setMapOutputKeyClass与setMapOutputValueClass原因
一般的mapreduce的wordcount程序如下: public class WcMapper extends Mapper<LongWritable, Text, Text, LongWr ...
- [转]OpenGL进阶(一) - 多视口
直接给出原文链接:OpenGL进阶(一) - 多视口
- 学习Kali Linux必须知道的几点
Kali Linux 在渗透测试和白帽子方面是业界领先的 Linux 发行版.默认情况下,该发行版附带了大量入侵和渗透的工具和软件,并且在全世界都得到了广泛认可.即使在那些甚至可能不知道 Linux ...
- scrapy shell
一.scrapy shell 1.安装pip install Jupyter 2.在pycharm中的启动命令: scrapy shell 注:启动后关键字高亮显示 3.查看response 执行sc ...
- Android自定义布局的背景在多分辨率的情况下设置fill_parent时背景不能够横向全屏的问题解决
问题描述:最近做了一个自定义的控件LinearLayout就是公用的底部菜单条,在指定分辨率下(例如:480x800,480x854)下背景是正常的,但是当程序运行到非指定(默认)的分辨率下就不正常了 ...