C#學習筆記--資料結構、泛型、委託事件等進階知識點

暢知發表於2023-10-17

C#進階

簡單資料結構類

ArrayList

元素型別以Object型別儲存,支援增刪查改的陣列容器。

因而存在裝箱拆箱操作,謹慎使用。

//ArrayList
ArrayList array=new ArrayList();
//增=================
array.Add("Hello");
array.Add(true);
array.Add("Tony");//新增單個元素
array.Add("Hello");
array.Add("World");

ArrayList array2=new ArrayList();
array2.Add("123");
array.AddRange(array2);//從陣列新增元素

//插入
array.Insert(1,"456");

//刪除==================
array.Remove("Hello");
array.RemoveAt(1);//根據索引移除元素
array.Clear();//清空
//查=================
array[0];
//檢視元素是否儲存在
if(array.Contains(true))
{
    
}
//正向查詢某個元素
//返回索引  未找到則返回-1
array.IndexOf("Hello");
//反向查詢元素位置
array.LastIndexOf("Hello");
//改=======================
array[2]="123";

//遍歷======
//區別長度(已經使用的容量)和容量
//容量
array.Capacity;
for(int i=0;i<array.Count;i++)
{
    Console.WriteLine(array[i]);
}
//迭代器遍歷
foreach(object obj in array)
{
    Console.WriteLine(item);
}
//裝箱拆箱

ArrayList和陣列區別?

  1. ArrayList使用不用說明固定長度,陣列則需要
  2. 陣列儲存的是指定型別的,ArrayList是Object
  3. ArrayList存在裝箱拆箱,陣列不存在
  4. ArrayList陣列長度用Count獲取 而陣列長度為Length
  5. ArrayList中提供增刪方法,使用方便,陣列需要自己操作實現

Stack

stack的本質:也是Object陣列

//棧 先進後出
//宣告
Stack stack=new Stack();

//========增===壓棧
stack.Push(1);
stack.Push("23");
stack.Push("Tony");
stack.Push(66.6);
//=======取====彈棧
object o=stack.Pop();
//======查
//棧只能檢視棧頂的內容,無索引器不可根據索引檢視
Object look=stack.Peek();
//檢視元素中有無內容
if(stack.Contains("Tony"))
{
	Console.WriteLine("該元素存在!");    
}

//清空
stack.Clear();
//遍歷
foreach(object item in stack)
{
    Console.WriteLine(item);
    //從棧頂取元素
}
//將棧轉換為陣列
object[] stackArray=stack.ToArray();
//棧頂元素在陣列前部分
//迴圈彈棧
while(stack.Count>0)
{
    object obj=stack.Pop();
    Console.WriteLine(obj);
}
//存在裝箱拆箱

Queue

本質:也是一個object陣列

//佇列  先進先出
//宣告
Queue queue=new Queue();

//增
queue.Enqueue(1);
queue.Enqueue(3.15f);
queue.Enqueue("Tony");
//取
queue.Dequeue();
//查
queue.Peek();
if(queue.Contains(3.15f))
{
    //列印
}
//改 只能清空
queue.Clear();
//遍歷 
queue.Count();
foreach(object item in queue)
{
    Console.WriteLine(item);
}
//轉成陣列
object[] objs=queue.ToArray();
for(int i=0;i<objs.Length;i++)
{
    //列印
     Console.WriteLine(objs[i]);
}
//迴圈出列
while(queue.Count>0)
{
    object obj=queue.Dequeue();
    Console.WriteLine(obj);
}

Hashtable

本質:儲存也是以object儲存。雜湊表 ,基於hash程式碼組織起來的鍵值對

可以做到訪問效率是O(1)

//HashTable
//宣告
Hashtable hashtable=new  Hashtable();

//增加  鍵不能重複
hashtable.Add(1,"123");
hashtable.Add("name","TonyChang");
hashtable.Add(3,21);
//刪除 --只能透過鍵來刪除
hashtable.Remove(1);
hashtable.Remove("name");

//清空
hashtable.Clear();

//檢視 找不到為空
hashtable[1];
hashtable["name"];

//驗證是否存在
if(hashtable.Contains("name"))
{
   //根據鍵去查詢
}
if(hashtable.ContainsKey("name"))
{
    //根據鍵去查詢
}
if(hashtable.ContainsValue("TonyChang"))
{
    //根據值去查詢
}
//只能透過鍵來找值,反之不行

//遍歷
hashtable.Count;//鍵值對數
//不一定按照插入順序列印
//元素是無序的
foreach(object item in hashtable.Keys)
{
    Console.WriteLine("鍵"+item);
    Console.WriteLine("值"+hashtable[item]);
}
//遍歷值
foreach(object item in hashtable.Values)
{
    Console.WriteLine("值"+item);
}
//鍵值對遍歷
foreach(DictionaryEntry item in hashtable)
{
    Console.WriteLine("鍵"+item.Key+" 值"+item.Value);
}
//迭代器遍歷
IDictionaryEnumerator tcIDE=hashtable.GetEnumerator();
bool flag =tcIDE.MoveNext();
while(flag)
{
     Console.WriteLine("鍵"+tcIDE.Key+" 值"+tcIDE.Value);
     flag =tcIDE.MoveNext();
}
//存在裝箱拆箱

泛型

泛型

泛型實現了型別引數化,達到程式碼重用目的,透過型別引數化來實現同一份程式碼操作多種類想,

泛型相當於型別佔位符,定義類或者方法時使用替代符代表變數型別,

當真正使用類或方法時候再具體指定型別

泛型分類:泛型類,泛型方法,泛型介面

//泛型
class TestClass<T>
{
    public T value;
}
TestClass<int> t=new TestClass<int>();
t.value=666;

//泛型佔位符可以有多個
class TestClass2<T1,T2,K,M>
{
    public T1 value1;
    public T2 value2;
    public K value3;
    public M value4;
}
interface ITest<T>
{
    T Value
    {
        get;
        set;
    }
}
//繼承之後需要表明具體類型別
class Demo:ITest<int>
{
    public int Value
    {
        get;
        set;
    }
}

//泛型方法
class Test2
{
    public void TestFun<T>(T value)
    {
        Console.WriteLine(value);
    }
    public void TestFun<T>()
    {
        T t=default(T);
    }
    //作為返回值
    public T fun3<T>()
    {
        return default(T);
    }
}

Test2 tt=new Test2();
tt.TestFun<string>("Tony");

//泛型類中的泛型方法
class Test2<T>
{
    public T value;
    public void TestFun(T value)
    {
        //這是非泛型方法
    }
    //泛型方法
    //判斷"<>"有無
    public void fun4<K>(K value)
    {
        //列印        
    }
}

泛型約束

泛型約束:關鍵字 where

  1. 值型別 where T :struct
  2. 引用型別 where T :class
  3. 存在無參公共建構函式 where T :new ()
  4. 某個類本身或者其派生類 where T:類名
  5. 某個介面的派生型別 where T:介面名
  6. 另一個泛型型別本身或者派生型別 where T:另一個泛型字元

注:這裡的”T“ 可以換成其它的泛型字母

//泛型型別的約束
class Test<T> where T:struct
{
    public void fun1<M> where M:struct
    {
        
    }
}

class Test1<T> where T:class
{
    public void fun1<M> where M:struct
    {
        
    }
}
//注意抽象類的無參公共建構函式
//也被允許
class Test2<T> where T:new()
{
    public void fun1<M> where M:struct
    {
        
    }
}
//······
//約束的組合使用
class Test7<T> where T:class,new ()
{
    
}
//多個泛型有有約束
class Test8<T,K> where T:class,new() where K:struct
{
    
}

常用的泛型資料結構類

list

本質:泛型陣列

//List
List<int> list=new List<int>();
//新增
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(4);
//查
list[0];
//清空
list.Clear();
//移除
list.RemoveAt(1);
//檢視某個元素是否存在
if(list.Contains(1))
{
 	//該元素存在   
}
//查詢元素索引
//未找到返回-1
int index=list.IndexOf(4);
//反向查詢
//未找到返回-1
index=list.LastIndexOf(4);
//改
list[2]=66;

//插入
list.Insert(0,666);
//遍歷
//長度
list.Count;
//容量
list.Capacity;
for(int i=0;i<list.Count;i++)
{
    //列印
    Console.WriteLine(list[i]);
}
foreach(int item in list)
{
     Console.WriteLine(item);
}
//List指定型別 不存在裝箱拆箱

DIctionary

本質:有具體型別的hashtable

//Dictionary
//宣告
Dictionary<int,string> dictionary=new Dictionary<int,string>();
//增
dictionary.Add(1,"Hello");
dictionary.Add(2,"World");
dictionary.Add(3,"Tony");
dictionary.Add(4,"Chang");
//刪除
dictionary.Remove(1);
//清空
dictionary.Clear();
//查
//按鍵進行查
dictionary[3];
dictionary[5];//找不到則報錯!!!
//檢視是否存在
if(dictionary.ContainsKey(1))
{
    //存在
}
if(dictionary.ContainsValue("Tony"))
{
    //存在
}
//改
dictionary[1]="258";
//遍歷
dictionary.Count;//元素個數
foreach(int item in dictionary.Keys)
{
    Console.WriteLine(item);
    Console.WriteLine(dictionary[item]);
}
//遍歷所有型別的值
foreach(int item in dictionary.Values)
{
    Console.WriteLine(item);
}
//鍵值對一起查詢
foreach(KeyValuePair<int,string> item in dictionary)
{
    Console.WriteLine("鍵:"+item.Key+"值:"+item.Value);
}

順序儲存和鏈式儲存

資料結構

線性表:陣列、連結串列、棧、佇列

非線性表:樹、圖、堆、雜湊表

儲存結構:

  • 順序儲存
    • 陣列,List,ArrayList
    • Stack
    • Queue
  • 鏈式儲存
    • 單向連結串列
    • 雙向連結串列
    • 迴圈連結串列
//簡單的單向連結串列
//節點類
class LinkedNode<T>
{
    public T value;
    public LinkedNode<T> nextNode;
    public LinkedNode(T value)
    {
        this.value=value;
    }
}
//單向連結串列的簡單實現
class LinkdedList<T>
{
    public LinkedNode<T> head;
    public LinkedNode<T> last;
    public void Add(T value)
    {
        LinkedNode<T> node=new LinkedNode<T>(value);
        if(head==null)
        {
            head=node;
            last=node;
        }
        else
        {
            last.nextNode=node;
            last=node;
        }
    }
    public void Remove(T value)
    {
        if(head==null)
        {
            return;
        }
        if(head.value.Equals(value))
        {
            //如果只有一個節點
            //且還是要刪除的節點
            head=head.nextNode;
            if(head==null)
            {
                last==null;
            }
            return;
        }
        LinkedNode<T> node=head;//哨兵節點
        //走到目標節點前的一個節點
        while(node.nextNode!=null)
        {
            if(node.nextNode.value.Equals(value))
            {
                break;
            }
            node=node.nextNode;
        }
        //進行移除
        node.nextNode=node.nextNode.nextNode;
    }
}

鏈式和陣列的優缺點:

鏈式表的增、刪比較方便,只需要更改節點之間的聯絡即可。

陣列表查詢比較方便,可以根據索引直接定位到某個位置。

LinkedList

有型別的雙向連結串列。

//雙向連結串列
LinkedList<int> linkedList=new LinkedList<int>();

//增
//從尾部新增
linkedList.AddLast(10);
//從頭部加
linkedList.AddFirst(5);

//移除
linkedList.RemoveFirst();//移除頭節點
linkedList.RemoveLast();//移除尾節點
//移除元素
linkedList.Remove(5);

//清空
linkedList.Clear();

//查
//頭節點
LinkedListNode<int> first=linkedList.First;
//尾節點
LinkedListNode<int> last=linkedList.Last;

//找到某個節點
LinkedListNode<int> node=linkedList.Find(5);//找不到返回為null
//在某個節點之後新增一個節點
linkedList.AddAfter(node,15);
//在某個節點之後前新增一個節點
linkedList.AddBefore(node,12);

//判斷某一元素是否存在
if(linkedList.Contains(5))
{
    //連結串列中存在5
}

//改
//找到某一節點
//改變其數值
//找到某個節點
LinkedListNode<int> node1=linkedList.Find(5);//找不到返回為null
node1.Value=15;

//遍歷
foreach(int item in linkedList)
{
    //列印節點
    Console.WriteLine(item);
}

//從頭節點進行遍歷查詢
LinkedListNode<int> curNode=linkedList.First;
while(curNode!=null)
{
 	Console.WriteLine(curNode.Value);  
    curNode=curNode.Next;
}
//從尾部節點進行遍歷查詢
LinkedListNode<int> curNode=linkedList.Last;
while(curNode!=null)
{
 	Console.WriteLine(curNode.Value);  
    curNode=curNode.PreVious;
}

泛型棧和佇列

Stack<int> stack;

Queue<int> queue;

具體的api和非泛型的相同。不再贅述!


委託與事件

委託

委託是函式的容器,可以理解為表示函式的變數型別,用來儲存、傳遞函式。

委託的本質是一個類,用來定義函式的型別(返回值和引數的型別)

不同的函式必須對應各自“格式”的委託。

關鍵字:delegate

存在位置:namespace 中(一般在此處),class中

//委託
//帕斯卡命名法
delegate void MyFuns();//無參無返回值的委託(此型別函式的家)
delegate int MyFuns2(int a);//不可以重名!!!儘管型別不同也不可以
//預設為public的,一般不用private

class Program
{
    static void Main(string[] args)
    {
        //將Fun函式放入委託容器中
        MyFuns funs=new MyFuns(Fun);
        //呼叫委託(中的函式)
        funs.Invoke();
        //======或者
        MyFuns f2=Fun;//宣告委託
        f2();//呼叫委託
        //============================
        MyFuns2 funs2=new MyFuns2(Fun2);
        funs2.Invoke(2);  
       
        MyFuns2 f2=Fun2;//宣告
        f2(2);//呼叫
    }
    static void Fun()
    {
        Console.WriteLine("我是個函式!");
    }
    static void Fun4()
    {
        Console.WriteLine("我是個函式Fun4!");
    }
    static int Fun2(int a)
    {
        Console.WriteLine("我是另外一種型別函式");
        return a*2;
    }
}

委託變數是函式的容器:

委託常用在:

  1. 作為類的成員
  2. 作為函式的引數
//接上一個程式碼塊
class Test
{
    public MyFuns funs;
    public MyFuns2 funs2;
    //委託作為函式的引數
    public void TestFun(MyFuns funs,MyFuns2 funs2)
    {
        //先處理一些邏輯
        //邏輯 code
        //······
        //再執行委託方法
       this.funs=funs;
       this.funs2=funs2;
    }
}
//在上個程式碼塊Main中呼叫
//使用
Test t=new Test();
t.TestFun(Fun,Fun2);//傳遞的函式名字

委託中儲存多個函式(多播委託)

//多播委託
MyFuns funs3=null;
fun3+=Fun;
fun3+=Fun4;
fun3.Invoke();//執行
//本質:觀察者模式====
//老闆來了!可以執行委託,通知儲存在委託中的員工好好工作的函式執行。
//老闆走了!可以執行另一種委託,通知儲存在委託中的員工開始摸魚。(狗頭)
//委託輕鬆實現!

小點:委託中多次減去同一個函式,不會報錯。

委託執行之前最好要檢查是否為空!

系統提供的委託:

//系統定義好的委託
//Action是無參無返回值型別的委託
Action action=Fun;
action+=Fun4;
action();//執行

//可以傳引數無返回值的委託
Action<int> ac1;
Action<int,float> ac2;
//=========Action無返回值委託===========
//=========Func有返回值=================
//Func委託
Func<int> funcInt;//返回值為int 無引數的委託
Func<int,float> func2;//引數為int 返回值為float的委託
funcInt+=Funs;
static int Fun5()
{
    Console.WriteLine("我是函式Fun5");
    return 5;
}

//自定義泛型委託
delegate T MyFun3<T>(T value);

image

相當於給每個裝在進委託中的函式來一次單獨的分配一個新的委託,各自的函式都存入自己獨立的委託,然後呼叫單獨的委託便會執行在委託中的函式。(對foreach遍歷的理解)

委託練習:

大耳朵圖圖一家吃飯!

使用託通知大家吃飯。

namespace TC
{
  abstract class Person
    {
        public string name;
        public abstract void Eat();
    }
    class Father : Person
    {
        public Father(string name)
        {
            this.name = name;
        }
        public override void Eat()
        {
            Console.WriteLine("{0}來吃飯了", name);
        }
    }
    class Mother : Person
    {
        public Action ToEat;
        public Mother(string name)
        {
            this.name = name;
        }
        public override void Eat()
        {
            Console.WriteLine("{0}來吃飯了", name);
        }
        public void Cooking()
        {
            Console.WriteLine("{0}正在做飯,",name);
            Console.WriteLine("做完了");
            ToEat?.Invoke();
            Eat();
        }
    }
    class Son : Person
    {
        public Son(string name)
        {
            this.name = name;
        }
        public override void Eat()
        {
            Console.WriteLine("{0}來吃飯了", name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Father father = new Father("胡英俊");
            Mother mother = new Mother("張小麗");
            Son son = new Son("胡圖圖");
            mother.ToEat += father.Eat;
            mother.ToEat += son.Eat;
            mother.Cooking();
        }
    }
}

image

事件

事件是基於委託的存在,是安全的委託。

事件的使用:

  1. 事件是作為成員變數存在於子類中
  2. 其它用法和委託相同

事件和委託的不同之處:事件不能在類外部賦值(但可以進行+=,-=)和呼叫。(事件必須在類中賦值和使用)

注意:它只能作為成員存在於類和介面以及結構體中。

class Test
{
    //委託
    public Action myAction;
    //事件
    public event Action myEvent;
}

為什麼要用事件呢?

  1. 防止外部隨意的置空委託
  2. 防止外部隨意呼叫委託
  3. 事件對委託進行一次的封裝,使用更安全

匿名函式

匿名函式沒有函式名字,往往都配合事件和委託使用的。

基本語法:

delegate(引數列表)``{

//函式邏輯

};

何時使用:

  1. 函式中傳遞委託引數時
  2. 委託或事件賦值時候
//匿名函式
//無參 無返回
Action action = delegate()
{
   //匿名函式的宣告
    //匿名函式宣告,放置在委託容器中
    Console.WriteLine("我是匿名函式");
};
action();//呼叫委託時候 呼叫匿名函式

//有參 無返回值
Action<int,string> action2=delegate(int a,string b)
{
    Console.WriteLine(a);
    Console.WriteLine(b);
};//注意:匿名函式最後加分號
action2(100,"Tony");
//有返回值 無引數
Func<int> action3=delegate()
{
    return 666;
};
action3();
//一般情況下會作為函式引數傳遞
class Test
{
    public Action action;
    
    //作為引數
    public void DoSomething(int a,Action ac)
    {
        Console.WriteLine(a);
        ac();
    }
    //作為返回值
    public void GetFun()
    {
        return delegate(){
          //無引數無返回值的匿名函式
            //作為函式的返回值
        };
    }
}

//使用
Test t=new Test();
t.DoSomething(100,delegate(){
   Console.WriteLine("隨函式傳入的匿名函式邏輯"); 
});
//接受返回的匿名函式
Action ac3=t.GetFun();
//執行
ac3();
//或者一步到位
t.GetFun()();
//匿名函式的缺點:
//匿名函式新增到委託中,因為沒有名字,不方便進行管理。
//不能指定移除某個匿名函式

Lambda表示式

lambda只是匿名函式的簡寫形式,本質還是匿名函式

//Lambda表示式
//無參無返回值
Action action=()=>{
    Console.WriteLine("無參無返回值的");
};
//執行
action();

//====有引數 無返回值
Action action2=(int value)=>
{
    Console.WriteLine("有參無返回值的Lambda{0}",value);
};
//呼叫執行
action2(666);

//可以省略引數型別,引數型別和儲存其的容器(事件或委託)來判斷
Action<int> action3=(value)=>{
    Console.WriteLine("省略了引數型別的Lambda表示式的寫法{0}",value);
};
action(999);
//======有返回值=======
Func<string,int> action4=(value)=>{
   Console.WriteLine("有參有返回值的Lambda表示式{0}",value);
    return 666;
};
int a=action4("hhh");

閉包:

內層函式可以引用包含在它外層函式的變數,即便外層函式的執行已經終止。

注意:該變數的值並非建立變數時候的初始值,而是在外層函式處理過的最終值

//例如
static Func<int,int>TestFun(int i)
{
    return delegate(int v)
    {
        //內部函式佔用著外部函式的i
        return i*v;
    }
}
//該變數的值並非建立變數時候的初始值,而是在外層函式處理過的最終值。
class Test
{
    public event Action action;
    public Test()
    {
        int value=66;
        action=()=>
        {
          Console.WriteLine("佔用外部的value");  
        };
        //再次給事件中新增匿名函式
        for(int i=0;i<10;i++)
        {
            action+=()=>{
                //此時所有的i
                //為外部函式執行完迴圈之後的最終值
                //最終值i=10;
                Console.WriteLine(i);
            };
        }
        //此時action中共有個匿名函式
        //第一個函式中value=66
        //其餘for迴圈新增的匿名函式引數i的值均為10
        
        //對比:
        //再次給事件中新增匿名函式
        for(int i=0;i<10;i++)
        {
            int index=i;
            action+=()=>{
                //此時所有的i
                //為外部函式執行完迴圈之後的最終值
                //最終值i=10;
                Console.WriteLine(index);
            };
        }
          //第一個函式中value=66
          //中間的for迴圈新增的匿名函式引數i的值均為10
          //最後的for迴圈新增的匿名函式引數index的值則是0~9
          //因為新增的是index臨時變數,index的數值就是自身最終數值 而i還在for
          //迴圈中自增
    }
}

List排序

//List排序
List<int> list=new List();
list.Add(8);
list.Add(3);
list.Add(2);
list.Add(5);
list.Add(1);
list.Add(6);
list.Add(4);
list.Add(9);
list.Add(7);
//1 List自帶的排序
list.Sort();
//ArrayList中也有Sort排序! 但是object類如何排序? 拆箱比較?
//自定義類排序
//若想使用排序函式 要實現一個排序介面
class Item:IComparable<Item>
{
    public int money;
    public Item(int money)
    {
        this.money=money;
    }
    //排序規則函式
    //List.Sort()根據儲存元素的方法來做排序
    public int CompareTo(Item other)
    {
        //返回值>0往後排
        //返回值=0保持不變
        //返回值<0往前移
        if(this.money>other.money)
        {
            return 1;//this物件移動到other物件後面
        }else
        {
            return -1;//this物件移到other物件前面
        }
    }
}
List<Item> itemList=new List<Item>();
itemList.Add(new Item(55));
itemList.Add(new Item(98));
itemList.Add(new Item(35));
itemList.Add(new Item(72));
itemList.Add(new Item(89));
itemList.Sort();//自定義排序規則呼叫排序
//小理解:List類在呼叫Sort排序時候,會將元素型別 as為排序介面型別(里氏替換原則)
//然後呼叫其CompareTo方法進行比較。

//3 透過委託函式進行排序
class ShopItem
{
    public int id;
    public ShopItem(int id)
    {
        this.id=id;
    }
}
List<ShopItem> shopItems=new List<ShopItem>();
shopItems.Add(new ShopItem(2));
shopItems.Add(new ShopItem(1));
shopItems.Add(new ShopItem(6));
shopItems.Add(new ShopItem(5));
shopItems.Add(new ShopItem(3));
shopItems.Add(new ShopItem(4));

//傳入中的物件為
//列表中的兩個元素 在比較時候會傳入元素做比較
static int SortShopItem(ShopItem a,ShopItem b)
{
    if(a.id>b.id)
    {
        return 1;
    }else
    {
        return -1;
    }
}
//呼叫Sort排序
//使用Sort的帶有委託引數的過載函式
shopItems.Sort(SortShopItem);
//使用匿名內部類
shopItems.Sort(delegate (ShopItem a,ShopItem b)
{
     if(a.id>b.id)
    {
        return 1;
    }else
    {
        return -1;
    }
 });
//也可以使用Lambda表示式 
shopItems.Sort((a,b)=>
 {
     return a.id>b.id?1:-1;
 });
//遍歷結果
for(int i=0;i<shopItems.Count;i++)
{
    Console.WriteLine(shopItems[i].id);
}

協變和逆變

協變:和諧的變化。例如,里氏替換原則,父類可以裝子類,因此子類變父類是一種和諧的變化。string變成object(裝箱)也是協變。(可以理解小變大,順應形勢,理所應當。)

逆變:於協變相反,逆常規的變化,不正常的變化,子類裝父類,object變string就屬於協變。(可以理解大變小,強制轉換,扭扭捏捏)

協變和逆變是用來修飾泛型的,只有在泛型介面和泛型委託中修飾泛型字元。

關鍵字:

協變:out(順勢而為,順出)修飾的只能作為 返回值

逆變:in(逆流而上,逆進)修飾的只能作為 引數

//協變和逆變
//out修飾型別 作為返回值
delegate T TestFunOut<out T>();
//in修飾的泛型 只能作為引數
delegate void TestFunIn(in T)(T t);

結合里氏替換原則:

class Father
{
    
}
class Son:Father
{
    
}
class Program
{
    static void Main(string[] args)
    {
        TestFunOut<Son> os=()=>{
            return new Son();
        };
        TestFunOut<Father> of=os;//父型別的委託裝載子型別的委託,二者都是委託的返回值
        //符合out 協變 如果沒有用out修飾,則說明二者是不同返回值型別的
        //委託,不可以進行賦值。
        TestFunIn<Father> InF=(value)=>
        {
            
        };
        TestFunIn<Son> InS=InF;
        Ins(new Son());
        //子型別的委託接受父型別的委託,但二者都是屬於委託引數,可以理解為,
        //子型別的建構函式呼叫父型別的建構函式來構造,底層本質還是符合大裝小的原則
        //in修飾之後,才可以進行大小委託賦值。是一種逆變
    }
}

多執行緒

程式?

程式是執行中的程式,系統進行資源分配和排程的基本單位,是作業系統的基礎。

執行緒?

執行緒存在於程式中,是程式中的實際運作單位,是作業系統能夠進行運算排程的最小單位。一個程式中可以併發多個執行緒,他們共享利用程式中的堆疊資源。

多執行緒?

一個程式中除了必要的主執行緒之外,可以開啟其它的執行緒來完成一些計算處理。
一個程式中除了必要的主執行緒之外,可以開啟其它的執行緒來完成一些計算處理。

//執行緒
//宣告
Thread t=new Thread(MyThreadFun);
bool isRunning=true;
static void MyThreadFun()
{
    while(isRunning)
    {
         Thread.Sleep(10000);
         Console.WriteLine("新執行緒的執行函式");
    }   
}
//啟動
t.Start();
//後臺執行緒
//設定為後臺執行緒之執行緒受主執行緒的影響(主執行緒結束,其它執行緒也結束),後臺執行緒不會防止應用程式程式被終止
//如果不設定為後臺執行緒可能會導致程式無法正常關閉
t.IsBackground= true;

//關閉執行緒
//如果執行緒中有死迴圈一致執行的,要關閉執行緒
//主動結束死迴圈
//對while()判斷條件控制設為false
isRunning=false;
//或者透過對執行緒提供的方法
//不一定都通用
t.Abort();
t=null;

//執行緒休眠
Thread.Sleep(10000);
//多執行緒--執行緒鎖
//避免對一個資源同時操作會造成邏輯錯誤
//多執行緒中要使用lock 保證一個記憶體區一刻時間只能有一個執行緒訪問
Object obj=new Object();
//鎖的使用引用型別
lock(obj)
{
    //邏輯
}

前處理器指令

預處理是在源程式編譯成目標指令之前,可以簡單的幫助選擇一些條件是否要編譯執行。

常見的預處理指令:

#define Tony
#define Lili
#if Tony
   Console.WriteLine("Tony");
#elseif 
#endif Lili
   Console.WriteLine("Lili");
#else
    Console.WriteLine("其他人");
#undef Tony
    
#waring
#error 

反射和特性

反射:

程式正在執行時候,可以檢視其它程式集或者自身的後設資料。一個執行的程式檢視本身或者其它程式的後設資料的行為就叫反射。

我們知道,程式=資料+演算法,程式在執行過程中需要不斷地給它“投餵”資料養料,而反射就是獲取養料(類、函式,變數等)的過程。有了反射,在執行時候可以動態的獲取自身所需要的資源,可以提高程式的擴充性和靈活性。

程式集:

程式集是經由編譯器編譯得到的,供進一步編譯執行的那個中間產物。在WINDOWS系統中,它一般表現為字尾為·dll(庫檔案)或者是·exe(可執行檔案)的格式。

也就是說程式集是我們開發的專案中程式的集合,工程檔案編譯執行所需要執行的所有檔案資源都算作程式集中的內容。

後設資料:

後設資料就是用來描述資料的資料,例如在程式中,類、函式、變數等等資訊就是程式的後設資料,她們儲存在程式集中。

Type:

它是反射功能的基礎,訪問後設資料的主要方式.

Assembly

//Type
//獲取Type
//1 objet中的方法
int a=45;
Type type=a.GetType();
//2透過typeof獲取
Type type2=typeof(int);
//3透過類名字
//要說明類所在的名稱空間
Type type3=Type.GetType("System.Int32");
//====三個訪問方式得到的Type型別相同

//得到類的程式集的資訊 Assembly
Console.WriteLine(type.Assembly);
Console.WriteLine(type2.Assembly);
Console.WriteLine(type3.Assembly);

//===========================
class Test
{
    private int i=1;
    private int j=2;
    private string name;
    public Test(int i)
    {
        this.i=i;
    }
    public Test(int i,string str):this(i)
    {
        this.name=str;
    }
    public void Talk()
    {
        Console.WriteLine("123");
    }
}
//獲取類中所有的公共成員
Type type=typeof(Test);
MemeberInfo[] infos=type.GetMembers();
for (int i = 0; i < infos.Length; i++)
{
    Console.WriteLine(infos[i]);
}
//獲取所有的建構函式
ConstructorInfo[] ctors=type.GetConstructors();
for(int i=0;i<ctors.Length;i++)
{
    Console.WriteLine(ctors[i]);
}
//獲取其中一個建構函式並執行
//獲取無參建構函式
ConstructorInfo info=type.GetConstructor(new Type[0]);
//執行無參構造
Test obj=info.Invoke(null) as Test;//無參構造 所以要傳null
//得到一個引數的有參構造
ConstructorInfo info2=type.GetConstructor(new Type[]{typeof(int)});
obj = info2.Invoke(new object[]{5}) as Test;
//獲取兩個引數的建構函式
ConstructorInfo info3=type.GetConstructor(new Type[]{typeof(int),typeof(string)});
obj = info2.Invoke(new object[]{5,"Tony"}) as Test;//與獲取的型別引數型別匹配

//獲取類的公共成員變數
FieldInfo[] fieldInfos=type.GetFields();
for (int i = 0; i < fieldInfos.Length; i++)
{
    Console.WriteLine(fieldInfos[i]);
}
//得到指定名稱的公共成員變數
//獲取J
FieldInfo infoJ=type.GetField("j");
Console.WriteLine(infoJ);
//透過反射獲取和設定物件的數值
Test test=new Test();
test.j=99;
test.name="Tony";
//透過反射獲取某個物件的數值
infoJ.GetValue(test);
//設定某個物件的數值
infoJ.SetValue(test,100);
//=================
//獲取類的公共成員方法
//以string類為例
Type strType=typeof(string);
MethodInfo[] methods=strType.GetMethods();
for(int i=0;i<methods.Length;i++)
{
    Console.WriteLine(methods[i]);
}
//如果存在方法過載 使用type陣列來表示引數型別
MethodInfo subStr=strType.GetMethod("Substring",new Type[]{typeof(int),tyoeof(int)});
//呼叫獲取到的方法
string str="Hello,TonyChang";
object result=subStr.Invoke(str,new object[]{5,9});
Console.WriteLine(result);
//還可以透過
//GetEnumName GetEnumNames得列舉

//得事件
//GetEvent
//GetEvents

//得介面
//GetInterface
//GetInterfaces

//得屬性
//GetProperty
//GetPropertys

//===============Activator=====
//可以使用它來快捷例項化Type型別得物件
Type testType=typeof(Test);
Test testObj=Activator.CreateInstance(testType) as Test;//無參構造
testObj=Activator.CreateInstance(testType,99) as Test;//有參構造
testObj=Activator.CreateInstance(testType,99,"Tony") as Test;//有參構造(引數型別要對應)

//==============Assembly(程式集類)================
//主要用來載入其它程式集 載入之後用Type來使用其內容
//可以載入dll檔案等
//三種載入程式集的函式
//一般用來載入在同一檔案下的其它程式集
Assembly asembly2 = Assembly.Load("程式集名稱");
//一般用來載入不在同一檔案下的其它程式集
Assembly asembly = Assembly.LoadFrom("包含程式集清單的檔案的名稱或路徑");
Assembly asembly3 = Assembly.LoadFile("要載入的檔案的完全限定路徑");
//1.先載入一個指定程式集
Assembly asembly = Assembly.LoadFrom(@"C:\Users\MECHREVO\Desktop\CSharp");
Type[] types = asembly.GetTypes();
//遍歷列印   
for (int i = 0; i < types.Length; i++)
{
    Console.WriteLine(types[i]);
}           
//2.再載入程式集中的一個類物件 之後才能使用反射
Type icon = asembly.GetType("Lesson18_練習題.Icon");
MemberInfo[] members = icon.GetMembers();
for (int i = 0; i < members.Length; i++)
{
    Console.WriteLine(members[i]);
}           
//透過反射 例項化一個 icon物件
//首先得到列舉Type 來得到可以傳入的引數
Type moveDir = asembly.GetType("Lesson18_練習題.E_MoveDir");
FieldInfo right = moveDir.GetField("Right");
           
//直接例項化物件
object iconObj = Activator.CreateInstance(icon, 10, 5, right.GetValue(null));
//得到物件中的方法 透過反射
MethodInfo move = icon.GetMethod("Move");
MethodInfo draw = icon.GetMethod("Draw");
MethodInfo clear = icon.GetMethod("Clear");
//進行呼叫程式
Console.Clear();
while(true)
{
    Thread.Sleep(1000);
    clear.Invoke(iconObj, null);
    move.Invoke(iconObj, null);
    draw.Invoke(iconObj, null);
}
//注:呼叫得程式是繪製方塊的練習題

特性

本質是個類,可以對後設資料的解釋和說明,供反射獲取的時候來獲取被呼叫的後設資料的資訊。

系統生成的特性:

過時特性:

迭代器

迭代器iterator

有時也稱為游標,是一種的軟體設計模式,迭代器模式提供一個方法順序訪問一個聚合物件中的各個元素,並且不暴露其內部的標識。

實現了迭代器的類才可以使用foreach遍歷。

//迭代器
//繼承兩個介面實現
//游標移動
//迭代器
class MyList:IEnumerable,IEnumerator
{
    private int[] list;
    private int position = -1;
    public MyList()
    {
        list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    }
    //實現IEnumerable的介面方法
    public IEnumerator GetEnumerator()
    {
        Reset();//返回之前要將索引器回到0
        return this;
    }
    //=========================
    public object Current
    {
        get => list[position];
    }
    //移動游標
    public bool MoveNext()
    {
        ++position;
        return position < list.Length;
    }
    //重置游標 
    public void Reset()
    {
        position = -1;
    }
}
//遍歷!!Main方法中
MyList list = new MyList();
foreach(int item in list)
{
    Console.WriteLine(item);
}

試著理解foreach的本質:

  1. 先獲取要遍歷物件的IEnumerator,呼叫其中的GetEnumerator方法獲取
  2. 執行IEnumerator物件中的MoveNext方法
  3. 若返回為true,則繼續呼叫Current獲取得到值給item

使用語法糖實現迭代器

//迭代器
class MyList1:IEnumerable
{
    private int[] list;
    public MyList1()
    {
        list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    }
    //實現IEnumerable的介面方法
    public IEnumerator GetEnumerator()
    {
       for(int i=0;i<list.Length;i++)
       {
            //使用語法糖
            //yield return 是C#提供給我們的語法糖
            //所謂語法糖,也稱糖衣語法
            //主要作用就是將複雜邏輯簡單化,可以增加程式的可讀性
            //從而減少程式程式碼出錯的機會
            //要保留當前狀態,暫時返回出去
           yield return list[i];
           //其它兩個相關函式由系統自動生成
       }
    }
    //=========================
}
//遍歷!!Main方法中
MyList1 list1 = new MyList1();
foreach(int item in list1)
{
    Console.WriteLine(item);
}

使用語法糖為泛型陣列實現迭代器

//泛型實現迭代器類
class MyList<T> : IEnumerable
{
    private T[] array;

    public MyList(params T[] array)
    {
        this.array = array;
    }

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < array.Length; i++)
        {
            yield return array[i];
        }
    }
}

相關文章