前言
本篇博客为c#高级部分的学习笔记,练习过程中结合了unity编辑器,所有的代码输出都是Debug.Log(“unity”);,在unity的控制台输出
字符串string相关API的使用
string中部分方法:
1,CompareTo()方法,比较字符串的内容
2,Replace()用另一个字符或者字符串替换字符串中给定的字符或者字符串
3,Split()在出现给定字符的地方,把字符串拆分称一个字符串数组
4,SubString()在字符串中检索给定位置的子字符串
5,ToLower()把字符串转换成小写形式
6,ToUpper()把字符串转换成大写形式
7,Trim()删除首尾的空白
8,Concat()方法,合并字符串
9,CopyTo()方法,把字符串中指定的字符复制到一个数组中
10,Format()方法,格式化字符串
11,IndexOf()方法,取得字符串第一次出现某个给定字符串或者字符的位置
12,IndexOfAny()方法,
13,Insert()把一个字符串实例插入到另一个字符串实例的指定索引处
14,Join()合并字符串数组,创建一个新字符串
1.CompareTo()方法,比较字符串的内容
str1.CompareTo(str2);
比较字符串的内容,字符串字母逐个排序
若str1小于str2返回-1
若str1等于str2返回0
若str1大于str2返回1
string str1 = "cch";
string str2 = "oop";
Debug.Log (str1.CompareTo(str2)); //-1
Debug.Log (str1.CompareTo("cch")); //0
Debug.Log (str1.CompareTo ("aaa")); //1
2.Replace()用另一个字符或者字符串替换字符串中给定的字符或者字符串
string strRes = str1.Replace(oldCharOrString,newCharOrString);
string str1 = "cch";
char oldChar = 'c'; //或者string oldString = "";
char newChar = 'a';
strRes = str1.Replace (oldChar, newChar);
Debug.Log (strRes); //"aah"
3.Split()在出现给定字符的地方,把字符串拆分称一个字符串数组
string[] strs = str.Split (’-’);
string[] strs = "178-7950-8158".Split ('-');
foreach (string str in strs) {
Debug.Log (str);
}
//输出178 7950 8158三个字符串
4.SubString()在字符串中检索给定位置的子字符串
从字符串str中截取子字符串
string strRes = str.Substring (index, length);
index表示开始截取的索引
length表示截取的长度
string str = "cchoop";
int index = 2;
int length = 2;
string strRes = str.Substring (index, length);
Debug.Log (strRes); //"ho"
5.ToLower()把字符串转换成小写形式
string strRes = str.ToLower();
将字符串str所有的英文字母转换为小写
string str = "cChoOp123";
string strRes = str.ToLower ();
Debug.Log (strRes); //"cchoop123"
6.ToUpper()把字符串转换成大写形式
string strRes = str.ToUpper();
将字符串str所有的英文字母转换为大写
string str = "cChOop123";
string strRes = str.ToUpper ();
Debug.Log (strRes); //"CCHOOP123"
7.Trim()删除首尾的空白
string strRes = str.Trim();
将首位的空白删除
注意:字符串中间的空白无法删除
string str = " cc hoop ";
string strRes = str.Trim ();
Debug.Log (strRes); //"cc hoop"
8.Concat()方法,合并字符串
string strRes = string.Concat(str1,str2,str3,…);
可以将多个字符串连接在一起得到一个新的字符串
string str1 = "cch";
string str2 = "oop";
string strRes = string.Concat (str1, str2, "123");
Debug.Log (strRes); // "cchoop123"
9.CopyTo()方法,把字符串中指定的字符复制到一个字符数组中
str.CopyTo(sourceIndex, chars, destinationIndex, count)
sourceIndex:字符串开始复制的索引
chars:接收复制的目标字符数组
destinationIndex:复制目标数组开始放入字符的索引
count:要复制的个数
string str = "cchoop";
char[] chars = new char[6];
int sourceIndex = 2; //字符串开始复制的索引
int destinationIndex = 3; //复制目标数组开始放入字符的索引
int count = 2; //要复制的个数
str.CopyTo(sourceIndex, chars, destinationIndex, count);
foreach (char c in chars)
{
Debug.Log(c);
}
//结果: (空) (空) (空) h o (空)
10.Format()方法,格式化字符串
string strRes = string.Format(format, str1, str2,…);
format:字符串格式
string str1 = "cch";
string str2 = "oop";
string format = "{0}-{1}";
string strRes = string.Format(format, str1, str2);
Debug.Log(strRes); //cch-oop
11.IndexOf()方法,取得字符串第一次出现某个给定字符串或者字符的位置
int index = str.IndexOf(‘o’);
string str = "cchoop";
int index = str.IndexOf('o');
Debug.Log(index); //3
12.IndexOfAny()方法,取得字符串第一次出现某个给定字符数组的位置
int index = str.IndexOfAny(chars);
chars:要查找任何字符所给定的字符数组
string str = "cchoop";
char[] chars = new char[] { 'h', '0' };
int index = str.IndexOfAny(chars);
Debug.Log(index); //2
13.Insert()把一个字符串实例插入到另一个字符串实例的指定索引处
string strRes = str.Insert(startIndex, value);
startIndex:指定的开始插入的索引
value:要插入的字符串
string str = "cch";
string value = "oop"; //要插入的字符串
int startIndex = 2; //要插入的位置索引
string strRes = str.Insert(startIndex, value);
Debug.Log(strRes); //ccooph
14.Join()合并字符串数组,创建一个新字符串
1.string strRes1 = string.Join(separator, strs);
2.string strRes2 = string.Join(separator, strs, startIndex, count);
separator:字符串连接处的字符串
strs:要合并的字符串数组
startIndex:字符串数组中要连接字符串开始的索引
count:要连接字符串的个数
string[] strs = new string[] { "cch", "oop", "Hello", "World" };
string separator = "->"; //字符串连接处的字符串
string strRes1 = string.Join(separator, strs);
Debug.Log(strRes1); //cch->oop->Hello->World
int startIndex = 1; //字符串数组中要连接字符串开始的索引
int count = 2; //要连接字符串的个数
string strRes2 = string.Join(separator, strs, startIndex, count);
Debug.Log(strRes2); //oop->Hello
StringBuilder的使用(频繁连接字符串时使用)
1.StringBuilder与String的区别
》String字符串内容不可变,因此String类型在做字符串的连接操作时,效率是相当低的
》StringBuilder对象在做字符串连接操作时是在原来的字符串上进行修改,改善了性能。
》结论:连接操作频繁的时候建议使用StringBuilder对象
2.创建StringBuilder对象
引入命名空间System.Text(using System.Text)
//初始化一个字符串
StringBuilder sb1 = new StringBuilder("cchoop");
//初始化一个空的字符串对象,占用了20个字符空间大小
StringBuilder sb2 = new StringBuilder(20);
//初始化了一个字符串,并占用20个字符空间大小
StringBuilder sb3 = new StringBuilder("cchoop", 20);
3.Append()追加字符串
sb.Append(“oop”).Append(‘a’,2);
向当前字符串末尾追加一个字符串或字符(字符可指定重复次数)
StringBuilder sb = new StringBuilder("cch");
sb.Append("oop");
Debug.Log(sb); //cchoop
4.Insert()插入字符串
sb.Insert(index, “oop”);
index:插入原字符串中开始位置的索引
StringBuilder sb = new StringBuilder("cch");
int index = 2;
sb.Insert(index, "oop");
Debug.Log(sb); //ccooph
5.Remove()从当前字符串中删除字符
sb.Remove(startIndex, length);
startIndex:开始删除的位置索引
length:要删除的字符个数
StringBuilder sb = new StringBuilder("cchoop");
int startIndex = 1;
int length = 2;
sb.Remove(startIndex, length);
Debug.Log(sb); //coop
6,Replace()在当前字符串中,用某个字符或者字符串全部替换另一个字符或者字符串
sb.Replace(oldCharOrString, newCharOrString);
oldCharOrString:要被替换的字符或者字符串
newCharOrString:替换后新的字符或者字符串
StringBuilder sb = new StringBuilder("cchoop");
string oldCharOrString = "oo";
string newCharOrString = "aaa";
sb.Replace(oldCharOrString, newCharOrString);
Debug.Log(sb); //cchaaap
7,ToString()把当前stringBuilder中存储的字符串,提取成一个不可变的字符串
string str = sb.ToString();
注意:1.ToString()方法返回值为string类型,使用之后字符串不可变
2.以上除了ToString()方法外,其它方法的返回值都是StringBuilder类型,且指向对象自身
StringBuilder sb = new StringBuilder("cchoop");
string str = sb.ToString();
Debug.Log(str); //cchoop
委托,Lambda表达式和事件
委托(delegate,Action,Func)
1.什么是委托
如果我们要把方法当做参数来传递的话,就要用到委托。简单来说委托是一个类型,这个类型可以赋值一个方法的引用。
2.声明委托
在我们使用委托的时候,需要经过这两个阶段,首先定义委托,告诉编译器我们这个委托可以指向哪些类型的方法,然后,创建该委托的实例。
定义委托的语法如下:
delegate int Calculator(int a, int b);
定义了一个委托叫做Calculator,这个委托可以指向什么类型的方法呢?
这个方法要带有一个int类型的参数,并且方法的返回值是int的。
定义一个委托要定义方法的参数和返回值,使用关键字delegate定义。
定义委托的其他案例:
delegate double TwoLongOp(long first,long second);
delegate string GetAString();
3.使用委托
》委托的赋值两种方式:
1.只需要把方法名给一个委托的构造方法就可以了
Calculator calculator = new Calculator(Add);
2.方法名直接给委托的实例
Calculator calculator = Add;
》通过委托示例调用方法有两种方式:
1.int result = calculator(3, 2);
2.int result = calculator.Invoke(3, 2);
using UnityEngine;
public class DelegateStudy : MonoBehaviour {
delegate int Calculator(int a, int b);
void Start () {
//Calculator calculator = new Calculator(Add);
Calculator calculator = Add;
//int result = calculator(3, 2);
int result = calculator.Invoke(3, 2);
Debug.Log(result); //5
}
private int Add(int a,int b)
{
return a + b;
}
}
4.使用委托类型当做一个方法的参数
void ExcuteMethod(Method method);
using UnityEngine;
public class DelegateStudy : MonoBehaviour {
delegate void Method();
void Start () {
Method method = method1;
method += method2;
ExcuteMethod(method);
//输出结果:method1 method2
}
void ExcuteMethod(Method method)
{
method();
}
void method1()
{
Debug.Log("method1");
}
void method2()
{
Debug.Log("method2");
}
}
5.Action委托
Action为系统内置的委托类型
Action< T,T…>委托引用了一个void返回类型的方法,无返回值,可以至多有16个参数,至少0个参数
using System;
using UnityEngine;
public class ActionStudy : MonoBehaviour {
void Start () {
//无参数
Action PrintHelloWorld = HelloWorld;
PrintHelloWorld(); //HelloWorld
//两个参数
Action<int, int> PrintCalculator = Add;
PrintCalculator(2, 3); //5
}
private void HelloWorld()
{
Debug.Log("Hello World!");
}
private void Add(int a,int b)
{
Debug.Log(a+b);
}
}
6.Func委托
Func为系统内置的委托类型
Func< T,T…,outT>委托引用了一个以outT为返回类型的方法,可以至多有16个参数,至少1个参数,且最后一个参数为返回值类型
using System;
using UnityEngine;
public class FuncStudy : MonoBehaviour {
void Start () {
//无参数是
Func<string> SayHelloWorld = HelloWorld;
Debug.Log(SayHelloWorld()); //Hello World
//一个参数
Func<int, string> PrintString = IntToString;
Debug.Log(PrintString(5)); //5
//两个参数,返回值为int
Func<int,int,int> Calculator = Add;
Debug.Log(Add(2, 3)); //5
}
private string HelloWorld()
{
return "Hello World";
}
private string IntToString(int num)
{
return num.ToString();
}
private int Add(int a,int b)
{
return a + b;
}
}
7.练习:对冒泡排序的拓展
冒泡排序拓展:通过冒泡排序对雇员进行排序(排序规则:按工资进行排序)
拓展描述:运用泛型T和委托对int型的冒泡排序进行改进
private void SortArray(List tList,Func<T,T,bool> compare)
tList:T类型的List集合
compare:委托类型,返回值为bool型,两个T类型参数
using UnityEngine;
using System.Collections.Generic;
using System;
//雇员类
class Employee
{
public string name { get; private set; }
public double emolument { get; private set; } //薪酬
public Employee(string name,double emolument)
{
this.name = name;
this.emolument = emolument;
}
public static bool Compare(Employee emp1,Employee emp2)
{
return emp1.emolument > emp2.emolument ? true : false;
}
public override string ToString()
{
return this.name + "的工资是:" + this.emolument;
}
}
public class TestStudy : MonoBehaviour {
void Start () {
//int型集合的冒泡排序
//List<int> intList = new List<int>() { 23, 56, 89, 78, 5, 3 };
//SortArray(intList,intCompare);
//foreach (int item in intList)
//{
// Debug.Log(item);
//}
//雇员类集合的冒泡排序(根据雇员工资进行排序)
List<Employee> empList = new List<Employee>()
{
new Employee("Tom",2000),
new Employee("cch",12000),
new Employee("oop",22000),
new Employee("Jery",5000),
new Employee("Black",1000)
};
SortArray(empList, Employee.Compare);
foreach (Employee emp in empList)
{
Debug.Log(emp);
}
}
private bool intCompare(int a,int b)
{
return a > b ? true : false;
}
//冒泡排序,适用任何类型T
private void SortArray<T>(List<T> tList,Func<T,T,bool> compare)
{
bool flag = false; //标记是否发生交换
do {
flag = false;
for (int i = 0; i < tList.Count - 1; i++)
{
if (compare(tList[i], tList[i + 1]))
{
T temp = tList[i];
tList[i] = tList[i + 1];
tList[i + 1] = temp;
flag = true;
}
}
} while (flag);
}
}
8.多播委托
前面使用的委托都只包含一个方法的调用,但是委托也可以包含多个方法,这种委托叫做多播委托。使用多播委托就可以按照顺序调用多个方法,多播委托只能得到调用的最后一个方法的结果,一般我们把多播委托的返回类型声明为void。
Action action1 = Test1;
action2+=Test2;
action2-=Test1;
注意:多播委托包含一个逐个调用的委托集合,如果通过委托调用的其中一个方法抛出异常,整个迭代就会停止。
using UnityEngine;
using System;
public class ManyDelegateStudy : MonoBehaviour {
void Start () {
Action<int,int> Calculator = Add;
Calculator += Multiply; //增加委托中调用的方法
Calculator += Multiply; //一个方法可以多次添加
Calculator += Minus;
Calculator -= Minus; //取消委托中的方法,但只能取消同种方法中的一个
//执行,如果委托方法为空,会抛出异常
//if (Calculator != null)
//{
// Calculator(6, 3);
//}
//取得多播委托中的所有方法
Delegate[] delegates = Calculator.GetInvocationList();
foreach (Delegate del in delegates)
{
del.DynamicInvoke(3, 6);
}
}
private void Add(int a,int b)
{
Debug.Log(a + "+" + b + "=" + (a + b));
//return a + b;
}
private void Multiply(int a,int b)
{
//throw new Exception("error!"); //抛出异常,迭代停止
Debug.Log(a + "X" + b + "=" + a * b);
}
private void Minus(int a, int b)
{
Debug.Log(a + "-" + b + "=" + (a - b));
}
}
9.匿名方法
到目前为止,使用委托,都是先定义一个方法,然后把方法给委托的实例。但还有另外一种使用委托的方式,不用去定义一个方法,应该说是使用匿名方法(方法没有名字)。
一般进行递归回调方法时使用匿名方法
Func<int,int,int> Calculator = delegate(int a, int b){};
using System;
using UnityEngine;
public class NoNameFunctionStudy : MonoBehaviour {
void Start () {
Func<int,int,int> Calculator = delegate(int a, int b) {
return a + b;
};
Debug.Log (Calculator (5, 6)); //11
}
}
Lambda表达式(=>)
1.Lambda表达式的使用
Lambda表达式代替了匿名函数,书写比匿名函数更加简洁
Lambda运算符“=>”的左边列出了需要的参数,如果是一个参数可以直接写 a=>(参数名自己定义),如果多个参数就使用括号括起来,参数之间以","间隔
Func<int,int,int> Calculator = (arg1, arg2) => {方法体};
using System;
using UnityEngine;
public class LambdaStudy : MonoBehaviour {
void Start () {
Func<int,int,int> Calculator = (a, b) => {
return a+b;
};
Debug.Log (Calculator (1, 2)); //3
}
}
2.Lambda表达式简写形式
1.当Lambda表达式的参数只有一个时,可以不加括号
Func<int,int> Calculator = arg1 => {方法体 return result;};
2.当Lambda表达式的函数体部分只有一条语句时,可以不加花括号“{}”和return语句
Func<int,int> Calculator = arg1 => result;
Func<int,String> PrintString = intA => intA.ToString ();
Debug.Log (PrintString (233)); //233
事件(Event)
事件(event)基于委托,为委托提供了一个发布/订阅机制,我们可以说事件是一种具有特殊签名的委托。
1.什么是事件?
事件(Event)是类或对象向其他类或对象通知发生的事情的一种特殊签名的委托.
事件的声明
public event 委托类型 事件名;
事件使用event关键词来声明,他的返回类值是一个委托类型。
通常事件的命名,以名字+Event 作为他的名称,在编码中尽量使用规范命名,增加代码可读性。
事件与委托的区别是:事件只能被声明为类的成员变量,事件不能在类的外部被触发
2.设计模式-观察者模式
为了更加容易理解事件,我们还是以前面的动物的示例来说明,有三只动物,猫(名叫Tom),还有两只老鼠(Jerry和Jack和Jay),当猫叫的时候,触发事件(CatShout),然后两只老鼠开始逃跑(MouseRun)。接下来用代码来实现。(设计模式-观察者模式)
using UnityEngine;
using System;
//猫类
class Cat
{
public string name { get; private set; } //猫的名字
public string color { get; private set; } //猫的颜色
public event Action<Cat> MouseRun;
public Cat(string name, string color)
{
this.name = name;
this.color = color;
}
public void CatShout()
{
Debug.Log(this.color+"的猫"+this.name+"来了,喵喵");
if (MouseRun != null)
{
MouseRun(this);
}
}
}
//鼠类
class Mouse
{
private string name; //老鼠的名字
private string color; //老鼠的颜色
public Mouse(string name,string color,Cat cat)
{
this.name = name;
this.color = color;
cat.MouseRun += MouseRun;
}
private void MouseRun(Cat cat)
{
Debug.Log("唧唧," + cat.color + "的猫" + cat.name + "来了," + this.color + "的老鼠" + this.name + "快跑");
}
}
public class EventStudy : MonoBehaviour {
void Start () {
Cat cat = new Cat("Tom", "灰色");
Mouse mouse1 = new Mouse("Jerry", "黄色", cat);
Mouse mouse2 = new Mouse("Jack", "黄色", cat);
Mouse mouse3 = new Mouse("Jay", "黄色", cat);
//cat.MouseRun(); //无法触发
cat.CatShout();
}
}
LINQ查询
1.LINQ的表达式写法
var res = from m in list
where “条件”
select m;
2.LINQ的扩展方法写法
var res = list.Where(Func< T,bool> method);
3.LINQ的联合查询(表达式写法)
var res = from l1 in list1
from l2 in list2
where “连接条件”
orderby l1.属性 //根据某个属性进行排序
select new {newL1 = l1, newL2 = l2}; //构造一个匿名对象接受联合查询结果
4.LINQ的联合查询(扩展方法写法)
//第一个委托参数指定和哪个list做连接,第二个委托参数组拼结果
var res = master.SelectMany(m => kongfu, (m, k) => new { mt = m, kf = k }).Where(mf => mf.mt.Kungfu == mf.kf.KongfuName && mf.kf.Lethality >= 90).OrderBy(mf => mf.mt.Level).ThenBy(mf => mf.mt.Age).ThenByDescending(mf => mf.kf.Lethality);
5.orderBy对结果进行排序
表达式写法:orderby 属性1,属性2 descending //先根据属性1排序,属性1相等的情况下再根据属性2排序,加上“descending”关键字,降序排列
扩展方法写法:list.OrderBy(Func< 集合元素,bool>).ThenByDescending(Func< 集合元素,bool>).ThenBy(Func< 集合元素,bool>)…; 带有Descending的方法为降序
6.join on集合联合查询
join 连接集合元素变量名 in 进行连接的集合 on 被连接的属性 equals 进行连接的属性
var res = from m in master
join k in kongfu on m.Kungfu equals k.KongfuName
where k.Lethality >= 90
orderby m.Level descending
select new { mt = m, kf = k };
7.分组操作第一种方式
into 分组变量名
var res = from k in kongfu
join m in master on k.KongfuName equals m.Kungfu
into groups
select new { kf = k, count = groups.Count() };
8.分组操作第二种方式
group 集合元素变量名 by 属性(根据此属性进行分组)
into 分组变量名
var res = from m in master
group m by m.Kungfu
into g
select new { count = g.Count(), key = g.Key };
9.量词操作符
》list.Any(Func< 集合元素变量名,bool>); //集合里面只要有一条数据条件满足,就会返回true
》list.All(Func< 集合元素变量名,bool>); //集合里面必须所有条件都满足才会返回true,否则返回false
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
/// <summary>
/// 类:武林高手
/// Martial arts master.
/// </summary>
class MartialArtsMaster
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Menpai { get; set; }
public string Kungfu { get; set; }
public int Level { get; set; }
public override string ToString()
{
return string.Format("武林高手Id={0},姓名={1},年龄={2},门派={3},独门绝技={4},武功等级={5}", this.Id, this.Name, this.Age, this.Menpai, this.Kungfu, this.Level);
}
}
/// <summary>
/// 类:功夫类
/// Kongfu.
/// </summary>
class Kongfu
{
public int KongfuId { get; set; }
public string KongfuName { get; set; }
public int Lethality { get; set; }
public override string ToString()
{
return string.Format("[Kongfu: 功夫Id={0}, 功夫名={1}, 伤害值={2}]", KongfuId, KongfuName, Lethality);
}
}
public class LINQStudy : MonoBehaviour
{
void Start()
{
//初始化武林高手
var master = new List<MartialArtsMaster>(){
new MartialArtsMaster(){ Id = 1, Name = "黄蓉", Age = 18, Menpai = "丐帮", Kungfu = "打狗棒法", Level = 9 },
new MartialArtsMaster(){ Id = 2, Name = "洪七公", Age = 70, Menpai = "丐帮", Kungfu = "打狗棒法", Level = 10 },
new MartialArtsMaster(){ Id = 3, Name = "王强", Age = 22, Menpai = "华山", Kungfu = "葵花宝典",Level = 2 },
new MartialArtsMaster(){ Id = 4, Name = "郭靖", Age = 22, Menpai = "丐帮", Kungfu = "降龙十八掌",Level = 10 },
new MartialArtsMaster(){ Id = 5, Name = "任我行", Age = 50, Menpai = "明教", Kungfu = "葵花宝典", Level = 1 },
new MartialArtsMaster(){ Id = 6, Name = "东方不败",Age = 35, Menpai = "明教", Kungfu = "葵花宝典", Level = 10 },
new MartialArtsMaster(){ Id = 7, Name = "林平之", Age = 23, Menpai = "华山", Kungfu = "辟邪剑法", Level = 7 },
new MartialArtsMaster(){ Id = 8, Name = "岳不群", Age = 50, Menpai = "华山", Kungfu = "辟邪剑法", Level = 8 },
new MartialArtsMaster() { Id = 9, Name = "令狐冲", Age = 23, Menpai = "华山", Kungfu = "独孤九剑", Level = 10 },
new MartialArtsMaster() { Id = 10, Name = "梅超风", Age = 23, Menpai = "桃花岛", Kungfu = "九阴白骨爪", Level = 8 },
new MartialArtsMaster() { Id =11, Name = "黄药师", Age = 23, Menpai = "梅花岛", Kungfu = "弹指神通", Level = 10 },
new MartialArtsMaster() { Id = 12, Name = "风清扬", Age = 23, Menpai = "华山", Kungfu = "独孤九剑", Level = 10 }
};
//初始化武学
var kongfu = new List<Kongfu>(){
new Kongfu(){KongfuId=1, KongfuName="打狗棒法", Lethality=90},
new Kongfu(){KongfuId=2, KongfuName="降龙十八掌", Lethality=95},
new Kongfu(){KongfuId=3, KongfuName="葵花宝典", Lethality=100},
new Kongfu() { KongfuId= 4, KongfuName = "独孤九剑", Lethality = 100 },
new Kongfu() { KongfuId = 5, KongfuName = "九阴真经", Lethality = 100 },
new Kongfu() { KongfuId = 6, KongfuName = "九阴白骨爪", Lethality = 98 },
new Kongfu() { KongfuId = 7, KongfuName = "弹指神通", Lethality = 100 },
new Kongfu() { KongfuId = 8, KongfuName = "辟邪剑法", Lethality = 100 },
};
//找出等级大于等于9的武林高手
// var res = new List<MartialArtsMaster> ();
// foreach (var item in master) {
// if(item.Level>=8){
// res.Add (item);
// }
// }
//LINQ的表达式写法
// var res = from m in master
// where m.Level >= 8
// select m;
//LINQ的扩展方法写法
// var res = master.Where (m => m.Level >= 9 && m.Menpai=="丐帮");
//找出武功伤的害值大于等于90的大侠
//var res = from m in master
// from k in kongfu
// where m.Kungfu == k.KongfuName && k.Lethality >= 90
// orderby m.Level,m.Age descending //先根据武功等级排序,武功等级相等的情况下再根据年龄排序,加上“descending”关键字,降序排列
// select new { mt = m, kf = k };
//第一个委托参数指定和哪个list做连接,第二个委托参数组拼结果
//var res = master.SelectMany(m => kongfu, (m, k) => new { mt = m, kf = k }).Where(mf => mf.mt.Kungfu == mf.kf.KongfuName && mf.kf.Lethality >= 90).OrderBy(mf => mf.mt.Level).ThenBy(mf => mf.mt.Age).ThenByDescending(mf => mf.kf.Lethality);
//join on集合联合查询
//var res = from m in master
// join k in kongfu on m.Kungfu equals k.KongfuName
// where k.Lethality >= 90
// orderby m.Level descending
// select new { mt = m, kf = k };
//分组操作第一种方式
//查看每一种功夫,有多少人修炼
//var res = from k in kongfu
// join m in master on k.KongfuName equals m.Kungfu
// into groups
// select new { kf = k, count = groups.Count() };
//分组操作第二种方式
//查看每一种功夫,有多少人修炼
//var res = from m in master
// group m by m.Kungfu
// into g
// select new { count = g.Count(), key = g.Key};
//输出结果
//foreach (var item in res)
//{
// Debug.Log(item);
//}
//量词操作符
//集合里面只要有一条数据条件满足,就会返回true
//bool res = master.Any(m => m.Menpai == "丐帮"); //true
//bool res = master.Any(m => m.Menpai == "长留"); //false
//集合里面必须所有条件都满足才会返回true,否则返回false
bool res = master.All(m => m.Menpai == "丐帮"); //false
Debug.Log(res);
}
}
反射和特性
什么是元数据,什么是反射
1.程序是用来处理数据的,文本和特性都是数据,而我们程序本身(类的定义和BLC中的类)这些也是数据。
2.有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中。
3.程序在运行时,可以查看其它程序集或其本身的元数据。一个运行的程序查看本身的元数据或者其他程序集的元数据的行为叫做反射。
使用Type类来反射数据
1.抽象类Type类被设计用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息。
Person person = new Person ();
Type type = person.GetType ();
2.获取Type对象有两种方式:
》 Type t = myInstance.GetType();//通过类的实例来获取Type对象?》
在object类有一个GetType的方法,返回Type对象,因为所有类都是从object继承的,所以我们可以在任何类型上使用GetType()来获取它的Type对象
》Type t = typeof(ClassName);//直接通过typeof运算符和类名获取Type对象
3.重要字段和方法:
Name 属性 返回类型的名字
Namespace 属性 返回包含类型声明的命名空间
Assembly 属性 返回声明类型的程序集。
GetFields 方法 返回类型的public字段列表
GetProperties 方法 返回类型的public属性列表
GetMethods 方法 返回类型的public方法列表,包括继承自object的Equals、ToSting等方法
namespace unity{
class Person{
public string name{ get; set;}
public string sex;
private int age;
private void Say(){
}
}
public class ReflexStudy : MonoBehaviour {
void Start () {
Person person = new Person ();
Type type = person.GetType ();
string className = type.Name; //Person,返回类的名字
string nameSpace = type.Namespace; //unity,返回命名空间
Assembly assembly = type.Assembly; //Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null,返回类型的程序集
FieldInfo[] fieldInfos = type.GetFields (); //sex,返回类型的public字段列表
PropertyInfo[] proInfos = type.GetProperties(); //name,返回类型的public属性列表
MethodInfo[] methodInfos = type.GetMethods (); //输出所有public的方法名,包括继承自object类的方法Equals、ToSting等,返回类型的方法列表
foreach (MethodInfo info in methodInfos) {
Debug.Log (info.Name);
}
// Debug.Log (assembly);
}
}
}
使用Assembly类加载程序集
1.如何加载程序集
》Assembly assembly1 = Assembly.Load(“SomeAssembly”);根据程序集的名字加载程序集,它会在本地目录和全局程序集缓存目录查找符合名字的程序集。
》Assembly assembly2 = Assembly.LoadFrom(@“c:\xx\xx\xx\SomeAssembly.dll”)//这里的参数是程序集的完整路径名,它不会在其他位置搜索
》通过Type类取得Assembly:
Person person = new Person ();
Assembly assem = person.GetType ().Assembly;
2.Assembly对象的使用
》assembly.FullName; //获取程序集全名
》assembly.GetTypes (); //取得程序集中所有定义的类型
using UnityEngine;
using System;
using System.Reflection;
namespace unity{
class Person{
public string name{ get; set;}
public string sex;
private int age;
private void Say(){
}
}
public class ReflexStudy : MonoBehaviour {
void Start () {
Person person = new Person ();
Assembly assem = person.GetType ().Assembly;
Debug.Log (assem.FullName); //获取程序集全名
Type[] types = assem.GetTypes (); //取得程序集中定义的类型
foreach (var type in types) {
Debug.Log (type);
}
}
}
}
特性(了解)
1.什么是特性
》特性(attribute)是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。
》 将应用了特性的程序结构叫做目标
》 设计用来获取和使用元数据的程序(对象浏览器)叫做特性的消费者
》 .NET预定了很多特性,我们也可以声明自定义特性
2.Obsolete特性
Obsolete(message,error);在方法之上标记该特性,表示该方法已过时被弃用
message:方法过时的提示消息
error:表示该方法是否是不可以被使用,true表示不可以被使用,如果使用会报错,编译不通过;默认为false,可以被使用,但是会被警告
using System;
using UnityEngine;
public class AttributeStudy : MonoBehaviour {
[Obsolete("此方法已经被弃用,请使用新方法NewTest",false)]
void OldTest(){
Debug.Log ("测试方法OldTest");
}
void NewTest(){
Debug.Log ("测试方法NewTest");
}
void Start () {
OldTest ();
NewTest ();
}
}
3.Contional特性
Conditional特性允许我们包括或取消特定方法的所有调用。为方法声明应用Conditional特性并把编译符作为参数来使用。
定义方法的CIL代码本身总是会包含在程序集中,只是调用代码会被插入或忽略。
#define DoTrace
class Program{
[Conditional("DoTrace")]
static void TraceMessage(string str){
Console.WriteLine(str);
}
static void Main(){
TraceMessage("Start of Main");
Console.WriteLine("Doing work in Main.")
TraceMessage("End of Main");
}
}
线程、任务和同步
进程和线程的一个简单解释
1,计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
2,如果工厂的电力有限一次只能供给一个车间使用。也就是说一个车间开工的时候,其他车间就必须停工。背后的含义就是。单个CPU一次只能运行一个任务。(多核CPU可以运行多个任务)
3,进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
4,一个车间里,可以有很多工人,他们协同完成一个任务。
5,线程就好比车间里的工人。一个进程可以包括多个线程。
6,车间的控件是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享空间。
7,进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
8,一个防止他人进入的简单方法,就是门口加一把锁(厕所)。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
9,还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
10,这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
11,操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
创建线程的第一种方式:异步委托
创建线程的一种简单方式是定义一个委托,并异步调用它。 委托是方法的类型安全的引用。Delegate类 还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。
接下来定义一个方法,使用委托异步调用(开启一个线程去执行这个方法)
Func testThread = Test;
IAsyncResult ar = testThread.BeginInvoke (null, null);
//testThread.BeginInvoke (null, null);表示开启一个新的线程,去执行testThread委托
int result = testThread.EndInvoke (ar); //接受一个结果
using UnityEngine;
using System;
using System.Threading;
public class ThreadStudy : MonoBehaviour {
int Test(){
int num = 0;
for (int i = 0; i < 10; i++) {
Debug.Log ("====" + i);
Thread.Sleep (10);
num += i;
}
return num;
}
void Start () {
Func<int> testThread = Test;
IAsyncResult ar = testThread.BeginInvoke (null, null);
while (ar.IsCompleted == false) {
Thread.Sleep (1);
Debug.Log (".");
}
int result = testThread.EndInvoke (ar);
Debug.Log (result);
}
}
异步回调函数
testThread.BeginInvoke (OnCallBack, testThread);
倒数第二个参数是一个委托类型:表示回调函数,线程执行完毕回调这个函数
倒数第一个参数表示向回调函数中传递一个参数
using UnityEngine;
using System;
using System.Threading;
public class ThreadStudy : MonoBehaviour
{
int Test()
{
int num = 0;
for (int i = 0; i < 10; i++)
{
Debug.Log("====" + i);
Thread.Sleep(10);
num += i;
}
return num;
}
void Start()
{
Func<int> testThread = Test;
IAsyncResult ar = testThread.BeginInvoke(OnCallBack, testThread);
}
void OnCallBack(IAsyncResult ar)
{
Func<int> testThread = ar.AsyncState as Func<int>;
int result = testThread.EndInvoke(ar);
Debug.Log("这是回调函数" + result);
}
}
异步回调-Lambda表达式
待委托的结果的第2种方式是使用异步回调。在BeginInvoke的第三个参数中,可以传递一个满足AsyncCallback委托的方法,AsyncCallback委托定义了一个IAsyncResult类型的参数其返回类型是void。对于最后一个参数,可以传递任意对象,以便从回调方法中访问它。(我们可以设置为委托实例,这样就可以在回调方法中获取委托方法的结果)
using UnityEngine;
using System;
using System.Threading;
public class ThreadStudy : MonoBehaviour
{
int Test()
{
int num = 0;
for (int i = 0; i < 10; i++)
{
Debug.Log("====" + i);
Thread.Sleep(10);
num += i;
}
return num;
}
void Start()
{
Func<int> testThread = Test;
testThread.BeginInvoke(a =>
{
int result = testThread.EndInvoke(a);
Debug.Log("这是在lambda表达式中取得的结果,result=" + result);
}
, null);
}
}
等待句柄IAsyncResult.AsyncWaitHanlde
当我们通过BeginInvoke开启一个异步委托的时候,返回的结果是IAsyncResult,我们可以通过它的AsyncWaitHandle属性访问等待句柄。这个属性返回一个WaitHandler类型的对象,它中的WaitOne()方法可以等待委托线程完成其任务,WaitOne方法可以设置一个超时时间作为参数(要等待的最长时间),如果发生超时就返回false。
using UnityEngine;
using System;
using System.Threading;
public class ThreadStudy : MonoBehaviour
{
int Test()
{
int num = 0;
for (int i = 0; i < 10; i++)
{
Debug.Log("====" + i);
Thread.Sleep(101);
num += i;
}
return num;
}
void Start()
{
Func<int> testThread = Test;
IAsyncResult ar = testThread.BeginInvoke(null, null);
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000); //1000ms表示超时时间,如果等待了1000ms后,线程还没有结束,方法返回false,结束了返回true
int result = 0;
if (isEnd)
{
result = testThread.EndInvoke(ar);
}
Debug.Log(result); //0,线程大约耗费1010ms,大于1000ms,所以isEnd为false
}
}
创建线程的第二种方式:通过Thread类创建
》创建一个线程,使用Shart()方法启动
Thread thread = new Thread(方法参数);
thread .Start();
》传参数必须使用object类型
void DownLoadFile(object fileName){} //模拟下载一个文件
thread.Start(“需要传递的参数值”);
》获取当前线程的Id
Thread.CurrentThread.ManagedThreadId
》使用Lambda表达式代替方法参数
Thread thread = new Thread((fileName)=> {
Debug.Log(“开始下载一个文件:” + fileName);
Thread.Sleep(1000);
Debug.Log(“下载文件完成”);
});
thread.Start(“文件名”);
》传递一个类的成员方法作为线程调用的方法参数
可以利用类的成员变量作为传递的参数
MyThread myThread = new MyThread(“xxx.avi”, “www.xxx.bbs.com”);
Thread thread = new Thread(myThread.DownLoadFile);
thread.Start();
using System.Threading;
using UnityEngine;
class MyThread
{
private string fileName;
private string filePath;
public MyThread(string fileName,string filePath)
{
this.fileName = fileName;
this.filePath = filePath;
}
public void DownLoadFile() //模拟下载一个文件
{
Debug.Log("开始下载文件" + this.filePath + fileName);
Thread.Sleep(1000); //下载文件耗费时间1000ms
Debug.Log(fileName + "下载完成");
}
}
public class ThreadStudy2 : MonoBehaviour
{
void DownLoadFile(object fileName) //模拟下载一个文件,传参数必须使用object类型
{
Debug.Log(Thread.CurrentThread.ManagedThreadId + "开始下载文件" + fileName);
Thread.Sleep(1000); //下载文件耗费时间1000ms
Debug.Log(fileName + "下载完成");
}
void Start()
{
//Thread thread1 = new Thread(DownLoadFile);
//Thread thread2 = new Thread(DownLoadFile);
//thread1.Start("文件一");
//thread2.Start("文件二");
//Thread thread = new Thread((fileName)=> {
// Debug.Log("开始下载一个文件:" + fileName);
// Thread.Sleep(1000);
// Debug.Log("下载文件完成");
//});
//thread.Start("文件名");
MyThread myThread = new MyThread("xxx.avi", "www.xxx.bbs.com");
Thread thread = new Thread(myThread.DownLoadFile);
thread.Start();
}
}
前台线程与后台线程及对线程的控制
只要一个前台线程在运行,应用程序的进程就在运行,如果多个前台线程在运行,但是Main方法结束了,应用程序的进程仍然是运行的,直到所有的前台线程完成其任务为止。
在默认情况下,用Thread类创建的线程是前台线程。线程池中的线程总是后台线程。
在用Thread类创建线程的时候,可以设置IsBackground属性,表示它是一个前台线程还是一个后台线程。
例如:如果关闭Word应用程序,拼写检查器继续运行就没有意义了,在关闭应用程序的时候,拼写检查线程就可以关闭。
当所有的前台线程运行完毕,如果还有后台线程运行的话,所有的后台线程会被终止掉。
》thread.Abort (); //调用这个方法,会在终止要终止的线程中抛出一个ThreadAbortException类型的异常,我们可以try catch这个异常,然后在线程结束前做一些清理的工作。
》thread.Join (); //等待thread线程代码完成再执行下面的代码
using UnityEngine;
using System;
using System.Threading;
public class ThreadStudy : MonoBehaviour {
void DownLoadFile(){ //模拟下载文件
Debug.Log ("开始下载文件");
Thread.Sleep (2000);
Debug.Log ("文件下载完成");
}
void Start(){
Thread thread = new Thread (DownLoadFile);
thread.IsBackground = true; //设置为后台线程
thread.Start ();
}
}
第三种创建线程的方式:线程池
创建线程需要时间。 如果有不同的小任务要完成,就可以事先创建许多线程 , 在应完成这些任务时发出请求。 这个线程数最好在需要更多的线程时增加,在需要释放资源时减少。
不需要 自己创建线程池,系统已经有一个ThreadPool类管理线程。 这个类会在需要时增减池中线程的线程数,直到达到最大的线程数。 池中的最大线程数是可配置的。 在双核 CPU中 ,默认设置为1023个工作线程和 1000个 I/O线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。 如果有更多的作业要处理,线程池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。
》线程池的使用:
ThreadPool.QueueUserWorkItem(DownLoadFile); //DownLoadFile是带有一个参数的委托
using System.Threading;
using UnityEngine;
public class ThreadStudy3 : MonoBehaviour
{
void DownLoadFile(object state) //模拟下载一个文件
{
Debug.Log("开始下载一个文件" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000);
Debug.Log(Thread.CurrentThread.ManagedThreadId + "下载完成");
}
void Start()
{
ThreadPool.QueueUserWorkItem(DownLoadFile); //传递一个带有参数的方法,可以传递一些数据
ThreadPool.QueueUserWorkItem(DownLoadFile); //传递一个带有参数的方法,可以传递一些数据
ThreadPool.QueueUserWorkItem(DownLoadFile); //传递一个带有参数的方法,可以传递一些数据
}
}
第四种创建线程的方式:任务
》任务的介绍
在命名空间System.Threading.Tasks包含了类抽象出了线程功能,在后台使用的ThreadPool进行管理的。任务表示应完成某个单元的工作。这个工作可以在单独的线程中运行,也可以以同步方式启动一个任务。
任务也是异步编程中的一种实现方式。
》启动任务
TaskFactory tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod);
Task t2 = TaskFactory.StartNew(TaskMethod);
Task t3 = new Task(TaskMethod);
t3.Start();
》连续任务
如果一个任务t1的执行是依赖于另一个任务t2的,那么就需要在这个任务t2执行完毕后才开始执行t1。这个时候我们可以使用连续任务。
static void DoFirst(){
Console.WriteLine("do in task : "+Task.CurrentId);
Thread.Sleep(3000);
}
static void DoSecond(Task t){
Console.WriteLine("task "+t.Id+" finished.");
Console.WriteLine("this task id is "+Task.CurrentId);
Thread.Sleep(3000);
}
Task t1 = new Task(DoFirst);
Task t2 = t1.ContinueWith(DoSecond);
Task t3 = t1.ContinueWith(DoSecond);
Task t4 = t2.ContinueWith(DoSecond);
Task t5 = t1.ContinueWith(DoError,TaskContinuationOptions.OnlyOnFaulted);
》任务层次结构
我们在一个任务中启动一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行,如果父任务执行完了但是子任务没有执行完,它的状态会设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态就变成RunToCompletion
static void Main(){
var parent = new Task(ParentTask);
parent.Start();
Thread.Sleep(2000);
Console.WriteLine(parent.Status);
Thread.Sleep(4000);
Console.WriteLine(parent.Status);
Console.ReadKey();
}
static void ParentTask(){
Console.WriteLine("task id "+Task.CurrentId);
var child = new Task(ChildTask);
child.Start();
Thread.Sleep(1000);
Console.WriteLine("parent started child , parent end");
}
static void ChildTask(){
Console.WriteLine("child");
Thread.Sleep(5000);
Console.WriteLine("child finished ");
}
线程问题-争用条件
如果2个或者2个以上的线程访问相同的对象,并且对共享状态的访问没有同步,就会出现争用条件。
using System.Threading;
using UnityEngine;
public class ThreadStudy2 : MonoBehaviour {
private int flag = 100;
private int state = 5;
void ChangeState(){
this.state++;
if(state == 5){
Debug.Log ("state的值为5,不可能发生的事情发生了");
flag --;
}
this.state = 5;
}
void ThreadMethod(){
while(flag>0){
ChangeState ();
}
}
void Start () {
new Thread (ThreadMethod).Start();
new Thread (ThreadMethod).Start();
//结果为输出了100次"state的值为5,不可能发生的事情发生了"
}
}
》第一种解决争用条件的问题的方法:使用lock(锁)
使用lock锁保证共享资源只能被一个线程访问
注意:lock语句只能锁定个引用类型
下面是改进后的ThreadMethod方法
void ThreadMethod(){
lock(this){ //将当前对象用lock锁住,当一个线程调用当前对象的方法时,其他线程无法访问当前对象中的方法
while(flag>0){
ChangeState ();
}
}
}
》第二种解决争用条件的问题的方法:定义一个object类型的变量sync,将它用于lock语句
每次修改state的值的时候,都使用这个一个sync的同步对象。就不会出现争用条件的问题了。
下面是改进后的ChangeState方法:
void ChangeState(){
lock (sync) {
this.state++;
if (state == 5) {
Debug.Log ("state的值为5,不可能发生的事情发生了");
flag--;
}
this.state = 5;
}
}
线程问题-死锁
如何解决死锁
在编程的开始设计阶段,设计锁定顺序
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 =0;
while(true){
lock(s1){
lock(s2){
s1.ChangeState(i);
s2.ChangeState(i);
i++;
Console.WriteLine("Running i : "+i);
}
}
}
}
public void Deadlock2(){
int i =0;
while(true){
lock(s2){
lock(s1){
s1.ChangeState(i);
s2.ChangeState(i);
i++; Console.WriteLine("Running i : "+i);
}
}
}
}
}
var state1 = new StateObject();
var state2 = new StateObject();
new Task(new SampleTask(s1,s2).DeadLock1).Start();
new Task(new SampleTask(s1,s2).DeadLock2).Start();
(完)