System.String類(string爲別名)正則表達式
注:string建立的字符串是不可變的,一旦進行了初始化,就不能改變其內容了sql
string的聲明:string s = "....";
字符串長度:int length = s.Length;
比較string大小:直接使用 == 便可:(s == "xxx")
字符串的鏈接:直接使用 + 便可:s = "..." + s;
這裏s的修改不是直接修改其內容,而是從新賦值,耗費性能
-- 新建字符串用來存儲鏈接後的字符串,並使引用s指向這個新字符串,舊的進入GC
字符串中按索引取得字符:s[index];
遍歷字符串:for循環編程
經常使用方法:c#
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(" ", "");app
IndexOf() -- 取得字符串第一次出現某個給定字符串或者字符的位置
int index = s.IndexOf("dev"); // 返回第一個字符對應的index值,若不存在,則返回-1
Concat() -- 合併字符串
CopyTo() -- 把字符串中指定的字符複製到一個數組中
Format() -- 格式化字符串
Insert() -- 把一個字符串實例插入到另外一個字符串實例的制定索引處
Join() -- 合併字符串數組,建立一個新字符串
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
安裝:VS中,工具 -> 擴展和更新 -> 聯機 -> 搜索resharper -> 選擇ReSharper(圖標c#)
正則表達式 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,沒有其餘意義
定位元字符:
字符 說明
\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));
若是要把方法當作參數來傳遞的時候,就要經過委託
簡單而言:委託是一個類型,能夠賦值一個方法的引用給該類型
-- 以前的變量都是賦值數據的
委託的聲明:
使用一個類的兩個階段:
定義類:告訴編譯器這個類由什麼字段和方法組成
定義後可使用對象:用這個類去實例化對象
使用一個委託的兩個階段:
定義委託:告訴編譯器這個委託能夠指向哪些類型的方法
建立該委託的實例:
定義委託的語法:
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"); }
除了上一節提到的自定義的委託類型,系統內置/預約義的委託類型:Action和Func
Action委託指向的是返回值爲void,且沒有參數的方法
Action a = MethodName;
Action委託的擴展<泛型>:能夠指向返回值是void,並且有參數的方法 (最大支持16個參數)
-- 參數列表必須和指向的方法的參數列表順序對應
Action<int> a = MethodName; // 有一個int參數
Action<int, bool> a = MethodName; // 有一個int和一個bool參數
-- 指向重載方法時,系統會自動匹配參數合適的方法
Func委託指向的是有返回值,且沒有參數的方法
Func<int> f = MethodName; // 必須有一個泛型,表示返回值類型
Func委託的擴展:能夠指向有返回值,且有參數的方法 (最大支持16個參數)
Func<string, int> f = MethodName; // 參數爲string, 返回值爲int
-- 最後一個是返回類型,其餘的都是參數類型
冒泡排序:從小到大
第一輪開始:
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); }
多播委託:指向了多個方法的委託
用處:使用多播委託,能夠按照順序調用多個方法,可是隻能獲得最後一個方法的結果
通常把多播委託的返回類型聲明爲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); // 單獨調用 -- 若是須要參數,則需傳遞
}
以前使用委託都須要先定義一個方法,而後將方法指定給委託的實例。
匿名方法:
定義一個沒有方法名的方法 -- 本質就是方法,只不過沒有定義名字
用delegate關鍵字
不用聲明返回類型,直接在方法中返回便可
Func<int,int,int> plus = delegate (int a,int b){ return a + b; }; int res = plus(34,34); Console.WriteLine(res);
匿名方法沒法直接調用,只能經過委託進行調用
通常來講,匿名方法用於進行回調
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;
這個時候,若是沒有正確使用,會變得很是危險:
由於一個方法的使用通常是經過傳遞的參數決定的
而因爲能夠訪問外部變量,致使執行也會被外部變量的變化而影響,結果不可控
以前學了委託:
實例:
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. 事件的返回值是一個委託類型
觀察者設計模式:
有觀察者和被觀察者,當被觀察者作出某個操做時,觸發事件,全部觀察者作出對應操做
例: 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. 常使用委託來進行回調
好比作一個動畫,動畫完成後進行操做:
由於動畫須要時間來完成,所以傳遞給它一個委託,動畫執行完後調用委託
這個委託指向的方法就叫回調函數
常使用事件來進行外發接口,給其餘類進行註冊
實例:武林高手數據查詢
建立武林高手類 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方法也同樣。
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);
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);
另外一種聯合查詢: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
分組查詢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 -- 表示按照哪一個屬性分的組
用途:判斷
Any() --
實例:判斷集合當中是否有屬於丐幫的人
Any() 傳入一個判斷是非的委託
bool res = masterList.Any( m => m.Menpai == "丐幫");
// 若是有,則返回true
All() --
實例:判斷集合當中是否都屬於丐幫
All()使用方法和Any()相同
bool res = masterList.All( m => m.Menpai == "丐幫");
通常微軟的語言都支持LINQ的語法
LINQ能夠支持從不少數據源進行查詢
上述例子咱們都是對Objects進行查詢
還有xml, sql, dataset, entities等數據源
LINQ to Objects部分的命名空間是System.Linq
進程 - Process
任什麼時候刻,單核CPU只能運行一個進程,其餘進程處於等待狀態
一個進程至少包含一個線程,也能夠有多個線程
通常狀況下一個應用程序啓用一個進程
同一個進程的內存空間對於它的進程來講是共享的,每一個線程均可以享用這部份內存空間
好比進程中的變量,是它的線程均可以訪問的
互斥鏈 (Mutual Exclusion -- Mutex):防止多個線程同時讀寫某一塊內存區域
先使用的時候加鎖,後使用的人看到鎖就排隊,直到鎖打開再進行使用
信號量 (Semaphore):保證多個線程不會互相沖突
某些內存區域只能供給固定數目的線程使用,超過的線程須要排隊
這時當線程在使用時,鑰匙減1,沒有鑰匙時就須要排隊了。
Mutex能夠當作是Semaphore的一個特殊狀況 (n=1),可是由於Mutex簡單、效率高,所以能用mutex就用mutex
使用線程的狀況:
示例:在Main函數中,一段代碼用於下載文件,一段代碼用於移動文件
一個線程中的代碼是從上到下執行的,因而須要等待文件下載完,才能執行其餘代碼
-- 多線程:在一個線程中執行下載文件的代碼,在另外一個線程中移動文件,還有Main線程執行其餘代碼
-- 通常會對比較耗時的操做另外開啓一個線程
經過委託開啓線程 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);
上一節中使用循環來監測線程是否結束
還有兩種方式能夠檢測委託線程的結束:等待句柄和回調函數
等待句柄:
當咱們經過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 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(100, ar => { int res = f.EndInvoke(ar); Console.WriteLine(res); } , null); // 由於Lambda表達式能夠直接訪問到外部變量 // 所以沒有必要經過最後一個參數傳遞f進去
經過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();
前臺線程和後臺線程:
有一個前臺線程在運行的話,應用程序的進程就在運行
即若是有前臺線程在運行,可是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狀態
直到加入的線程完成爲止,再繼續執行下面的代碼
建立線程須要時間。
若是有不一樣的小任務要完成,就能夠事先建立許多線程 , 在應完成這些任務時發出請求。
這個線程數最好在須要更多的線程時增長,在須要釋放資源時減小。
系統自帶一個ThreadPool類,用於管理線程。
這個類會在須要時增減池中線程的線程數,直到達到最大的線程數。
在雙核 CPU中,默認設置爲1023個工做線程和 1000個 I/o線程。
也能夠手動指定在建立線程池時應該當即啓動的最小線程數,以及線程池中可用的最大線程數。
若是有更多的做業要處理,且線程池中線程的個數也到了極限,
那麼最新的做業就要排隊,且必須等待線程完成其任務。
線程池默認建立出來的任務都是後臺線程
注意:不能把線程池的線程修改成前臺線程
不能修改線程池中線程的優先級和名稱
線程池中的線程只能用於運行時間段的任務
實例:開啓工做線程
static void ThreadMethod(object state) {
// 須要一個object類的參數
}
Main() {
ThreadPool.QueueUserWorkItem(ThreadMethod);
// 若是想要傳遞數據,在ThreadMethod後寫上逗號和實參便可
經過任務開啓線程 -- 三種方式
第一種 -- 和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(2000); Console.WriteLine(parent.Status); Thread.Sleep(4000); Console.WriteLine(parent.Status); } static void ParentTask() { var child = new task(ChildTask); child.Start(); Thread.Sleep(1000); } static void ChildTask() { Thread.Sleep(5000); }
爭用條件 Race Conditions:
A race condition occurs when two threads access a shared variable at the same time.
多個線程同時訪問一個變量時,形成的讀寫衝突
實例:
線程要執行的委託指向的方法:
class MyThreadObject { private int state = 5; // shared variable public void ChangeState() { state++; if(state == 5) { // 目前來看這個條件永遠不會知足 輸出state=5; } state = 5; } }
在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 =0; 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 =0; 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的順序(或反過來)