C#学习笔记--数据结构、泛型、委托事件等进阶知识点

C#学习笔记--数据结构、泛型、委托事件等进阶知识点 委托事件等进阶知识点 学习笔记 数据结构 泛型 C# --

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