C#學習筆記--數據結構、泛型、委托事件等進階知識點

来源:https://www.cnblogs.com/TonyCode/archive/2023/10/17/17770866.html
-Advertisement-
Play Games

此博客是C#學習筆記中的進階部分,設計C#語言中的高級知識,介紹了List與ArrayList、Stack和Queue以及Hashtable等數據結構, 泛型,泛型類型數據結構,以及糾纏不清的委托與事件。還涉及到不常見但常用的一些知識小點,如匿名函數,Lambda表達式,協變和逆變,多線程, 反射和... ...


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];
        }
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、審核功能實現的方式 1、普通 方案:經辦時入A表,審核後從A表讀取數據,然後操作目標B表; 優勢:思路簡單 劣勢:對後端功能實行高度的嵌入;審核功能數據操作不統一 2、彈框式 方案:前臺實現,操作時判斷是否需要許可權控制,如果需要,則彈出框,由審核人員進行審核,審核通過後,進行後續操作。 優勢:對 ...
  • C語音-數據類型 數據類型 中文名稱 空間大小(bite - 位元組) char 字元串數據類 1 short (int) 短整型 2 int 整形 4 long 長整形 4 long long 更長的整形 8 float 單精度浮點數 4 double 雙精度浮點數 8 include <> int ...
  • 最近這段時間收到了一些讀者的私信,問我某個技術要不要學,還有一些在國外的同學竟然對 Java 圖形化很感興趣,還想找這方面的工作。 比較忙,一直沒抽出時間去回答這類問題,剛好看到我關註的一位大佬回答過,這裡分享一下,希望對你能有幫助。 下麵是正文。 原文鏈接:https://www.zhihu.co ...
  • 網路上的文件傳輸功能也是很有必要實現一下的,網路傳輸文件的過程通常分為客戶端和伺服器端兩部分。客戶端可以選擇上傳或下載文件,將文件分塊並逐塊發送到伺服器,或者從伺服器分塊地接收文件。伺服器端接收來自客戶端的請求,根據請求類型執行對應的操作,並根據發送的文件名或其他標識來確定要傳輸的文件。在實現文件傳... ...
  • 0 概述 通常聲明一個數組時需要使用一個常量來指定數組的長度,數組所占用的記憶體是在編譯時就被分配。這種方式的聲明的優點是簡單,但是存在以下幾個缺點: 使用的元素數量超過數組聲明的長度,當前數組就不能存儲相應的數據; 如果數組的長度被聲明很大,實際使用的元素又比較少會導致記憶體空間的浪費; 程式開發中會 ...
  • 正文 上一篇文章我們講解了事務的Advisor是如何註冊進Spring容器的,也講解了Spring是如何將有配置事務的類配置上事務的,實際上也就是用了AOP那一套,也講解了Advisor,pointcut驗證流程,至此,事務的初始化工作都已經完成了,在之後的調用過程,如果代理類的方法被調用,都會調用 ...
  • 如果多個客戶端同時請求修改同一個kubernetes資源,那麼很有可能收到apiserver返回失敗,本篇就來分析並復現這個問題,然後再實戰client-go官方的解決手段 ...
  • 你知道HTTP中的壓縮演算法是如何工作的嗎, 他們的壓縮比又是多少, 能起到多少作用嗎? 他的限制又是多少嗎? 他存在的意義給我們帶來了什麼? ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...