Siki_Unity_2-9_C#高級教程(未完)

Unity 2-9 C#高級教程

任務1:字符串和正則表達式
任務1-1&1-2:字符串類string

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() -- 合併字符串數組,建立一個新字符串

任務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 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進去

任務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(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);
}

任務5-9:線程中會遇到的問題 -- 爭用條件和死鎖

爭用條件 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的順序(或反過來)

任務6:網絡

任務7:文件操做

任務8:xml操做、jason操做和excel操做

相關文章
相關標籤/搜索