C#进阶篇

ArrayList

1、Arraylist本质上是一个可以自动扩容的object数组
2、由于用万物之父来存储数据,自然存在装箱拆箱
3、当往其中进行值类型存储时就是在装箱,当将值类型对象取出来转换使用时,就存在拆箱。
4、所以ArrayList尽量少用,之后我们会学习更好的数据容器(泛型)。

底层原理:

底层是通过数组实现的,每次容量不够的时候,声明一个容量翻倍的新数组,并且把原来的内容复制到新数组中。

Clear原理:

将数组的Count设为0,然后将所有元素的属性设为默认值(值类型为0,引用类型为null),Capacity不变,也就是说栈内存保持不变,释放的堆内存需要等下次GC时回收。

代码实现:

 static void Main()
    {
        //ArrayList数组类(万物之父类,任意类型)
        ArrayList array = new ArrayList();
        ArrayList array1 = new ArrayList();
        //增
        array.Add("123");
        //范围增加(批量增加把另一个list容器里面的内容加到后面)
        array.AddRange(array1);
        //删(从头删,找到就删)
        array.Remove(1);
        //移除指定位置的元素
        array.RemoveAt(0);
        //查
        Console.WriteLine(array[0].ToString() + array[1].ToString());
        //查看元素是否存在
        if (array.Contains("456"))
        {
            Console.WriteLine("存在456");
        }
        //正向查找元素位置(返回位置 找不到返回-1)
        int index = array.IndexOf("456");
        //反向查找元素位置
        index = array.LastIndexOf(0);
        //改
        array[index] = 19;
        //插入
        array.Insert(index, "opq");
        //遍历
        Console.WriteLine(array.Count);//长度
        Console.WriteLine(array.Capacity);//容量
        for (int i = 0; i < array.Count; i++)
        {
            Console.Write(array[i]+" ");
        }
        //迭代器遍历
        foreach (var item in array) // item = array[i](var = object)
        {
            Console.Write(item);
        }
    }

stack栈

先进后出集合,栈顶压栈顶弹,存在装箱拆箱

底层原理:

内部用一个泛型数组存储元素,添加元素时直接从尾部添加,当容量不够时进行扩容,扩容方式与List相同。


Queue队列

先进先出集合,头部取尾部添加,存在装箱拆箱

底层原理:

动态循环数组。内部用_head和_tail两个指针来标记头和尾,移除元素时只需要移动移动头和尾指针,并将原位置标记为null。


Hashtable散列表

Hashtable (又称散列表) 是基于键的哈希代码组织起来的键/值对,都是object类型,存在装箱拆箱。
它的主要作用是提高数据查询的效率O(1),使用键来访问集合中的元素。

底层原理:

1、散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个储存位置。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。
2、这种对应关系f又称为散列函数,又称为哈希(Hash)函数。按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash Table)。那么关键字对应的记录存储位置我们称为散列地址

增加元素:

装填因子一般为0.72。多则容易冲突,少则浪费空间。
计算比较(已有数据的个数/桶的数量值)和装填因子:
少于则:通过散列技术进行增加。
大于则:增加桶的数量即扩充数组,扩充到最小两倍当前数组大小的素数(3变为7),遍历原桶数组中的数据通过散列技术重新放入新的数组中。O(n)

哈希冲突解决:

开放定址法(线性探测):公式为(f(key)+di )% Array.length,通过该公式来寻找下一个位置放入数据。只要散列表足够大,空的散列地址总能找到。

代码实现:

class MyClass
{
    static void Main()
    {
        Hashtable ht = new Hashtable();

        //增(不能出现相同键)
        ht.Add(1, "123");
        ht.Add("12", "153");
        //删(不存在的,没反应)根据键
        ht.Remove(1);
        //清空
        //ht.Clear(); 
        //查找(找不到返回空)
        Console.WriteLine(ht[2]);
        //根据键查找是否存在(两个方法一样)
        if (ht.Contains(2))
        {
            Console.WriteLine("存在键" + ht[2]);
        }
        if (ht.ContainsKey(1))
        {
            Console.WriteLine("存在键" + ht[1]);
        }
        //根据值查找是否存在
        if(ht.ContainsValue("183"))
        {
            Console.WriteLine("存在值183");
        }
        //改(只能改值)
        ht["1"] = 456;
        //遍历
        Console.WriteLine(ht.Count); //多少对
        foreach (object item in ht.Keys) //得到所有键
        {
            Console.Write(item + " ");
            Console.Write(ht[item] + " ");
        }
        foreach (object item in ht.Values) //得到所有值
        {
            Console.Write(item + " ");
        }
        foreach (DictionaryEntry item in ht) //键和值一起得到
        {
            Console.WriteLine(item.Key +"-"+ item.Value);
        }
        //迭代器遍历法
        IDictionaryEnumerator dictionaryEnumerator = ht.GetEnumerator();
        bool flag = dictionaryEnumerator.MoveNext();
        while (flag)
        {
            Console.WriteLine(dictionaryEnumerator.Key + "-" + dictionaryEnumerator.Value);
            flag = dictionaryEnumerator.MoveNext();
        }
    }

泛型

泛型是什么?

1、泛型实现了类型参数化,达到代码重用目的。
2、通过类型参数化来实现同一份代码上操作多种类型。
3、泛型相当于类型占位符。
4、定义类或方法时使用替代符代表变量类型。
5、当真正使用类或者方法时再具体指定类型。

代码实现:

class Test<T>
{
    public T Id;
    //这个不属于泛型方法,T属于上面的
    public void TestFun(T t){
    }
    //这个属于泛型方法
    public void TestFun<K>(K k){
    }
}
//泛型占位字母可以有多个,用逗号分开
class Test1<T1,T2,T3>
{
    public T1 value;
    public T2 value1;
    public T3 value3;
}
//泛型接口
interface IInterface<T0>
{
    T0 Va{
        get;
        set;
    }
}
class inter : IInterface<int>
{
    public int Va { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}

//泛型方法
class Test2
{
    public void testfun<T>(T value)
    {
        Console.WriteLine(value);
    }
    public void testfun<T>()
    {
        T t = default(T);
        Console.WriteLine(t);
    }

    public T TestFun<T>(string s)
    {
        return default(T);
    }

    public void TestFun<T,K,L>(T t,K k,L l) {
    }
}
class MyClass
{
    static void Main()
    {
        Test<int> t = new Test<int>();
        Test<string> t1 = new Test<string>();
        t.Id = 10;
        t1.Id = "w1s";
        t1.TestFun("ss");
        t1.TestFun<int>(10);

        Test1<int, string, float> t3 = new Test1<int, string, float>();

        Test2 tt = new Test2();
        tt.testfun<int>(1234);
    }
}

泛型约束

泛型约束是什么?

让泛型的类型有一定的限制,如果不引入泛型约束的话,你会发现在代码块里面使用泛型的时候处处受阻。而引入之后,就预先给了这个泛型一些功能。

泛型约束一共有6种:

1、值类型-----------------------------where 泛型字母: struct
2、引用类型---------------------------where 泛型字母: class
3、存在无参公共构造函数-------------where 泛型字母: new()
4、某个类本身或者其派生类-----------where 泛型字母: 类名
5、某个接口的派生类型----------------where 泛型字母: 接口名
6、另一个泛型类型本身或者派生类型---where 泛型字母:另一个泛型字母
格式:where 泛型字母:(约束条件)

代码实现:
//1.值类型  
class Test1<T> where T : struct
{
    public T Value;
    public void TestFun<K>(K k) where K:struct
    {
    }
}

//2.引用类型
class Test2<T> where T : class
{
    public T Value;
    public void TestFun<K>(K k) where K : class
    {
    }
}

//3.存在无参公共构造函数非抽象类
class Test3<T> where T : new()
{
    public T Value;
    public void TestFun<K>(K k) where K : new()
    {
    }
}
class My
{
    //这里无参构造函数必须公共
}
class My1
{
    //有参构造函数不行
    public My1(int a){
    }
}

//4.某个类本身或者其派生类 
class Test4<T> where T : My1
{
    public T Value;
    public void TestFun<K>(K k) where K : My1
    {
    }
}
class MY : My1
{
    public MY(int a) : base(a)
    {
    }
}

//5.某个接口的派生类型
interface Fly
{
}
class fly : Fly
{
}
class Test5<T> where T : Fly
{
    public T Value;
    public void TestFun<K>(K k) where K : Fly
    {
    }
}

//6.另一个泛型类型本身或者派生类型
//1.要么T = U
//2.要么T是U的派生类
class Test6<T, U> where T : U
{
    public T Value;
    public void TestFun<K, V>(K k) where K : V
    {
    }
}


//二、约束的组合使用
//注意:new()要写到最后
class Test7<T> where T:class,new()
{
}

//三、多个泛型有约束
class Test8<T,U> where T:class,new() where U:struct
{
}

class MyClass
{
    static void Main()
    {
        Test1<int> t1 = new Test1<int>();
        t1.TestFun<float>(1.2f);

        Test2<object> t2 = new Test2<object>();
        t2.TestFun<object>(1.2f);

        Test3<My> t3 = new Test3<My>();
        t3.TestFun<int>(10);//构造函数符合

        Test4<My1> t4 = new Test4<My1>();
        MY t = new MY(12);
        t4.TestFun<MY>(t);//派生类也可以

        Test5<Fly> t5 = new Test5<Fly>();
        fly f = new fly();
        t5.TestFun<fly>(f);

        Test6<fly, Fly> t6 = new Test6<fly, Fly>();
    }
}

List

一个泛型集合类,它允许我们存储一个具有特定类型的元素的集合。List是可扩展的,也就是说,如果需要,我们可以动态增加或减少List中元素的数量。功能跟ArrayList差不多。

底层原理:

List的底层是通过数组实现的,当向List中添加元素时,如果数组已满,List会创建一个更大的新数组,并将所有元素复制到新数组中。这个过程称为重新分配和复制。

代码实现:

 List<int> list = new List<int>();

自定义排序:

//自定义类排序,必须继承接口 IComparable<>
class Item : IComparable<Item>
{
    public int money;

    public Item(int money){
        this.money = money;
    }

    //必须重写接口函数
    public int CompareTo(Item? other)
    {
        //返回值的含义,this会和其他的所有数据进行比较(other)
        //可以简单理解 传入对象的位置 就是0
        //如果你返回为负数 就放在它的左边 也就是前面
        //如果你返回为正数 就放在它的右边 也就是后面
        return this.money - other?.money;
    }
}

class ShopItem
{
    public int id;

    public ShopItem(int id){
        this.id = id;
    }
}

class MyClass
{
    static void Main()
    {

        //一、系统提供的排序
        List<int> list = new List<int>();
        list.Add(61);
        list.Add(27);
        list.Add(43);
        list.Add(41);
        list.Add(50);
        //排序
        list.Sort();

        //二、自定义类的排序
        List<Item> items = new List<Item>();
        items.Add(new Item(12));
        items.Add(new Item(52));
        items.Add(new Item(32));
        items.Add(new Item(72));
        items.Add(new Item(80));
        items.Add(new Item(13));
        //排序
        items.Sort();

        //三、通过委托函数进行排序
        List<ShopItem> items2 = new List<ShopItem>();
        items2.Add(new ShopItem(12));
        items2.Add(new ShopItem(42));
        items2.Add(new ShopItem(25));
        items2.Add(new ShopItem(69));
        items2.Add(new ShopItem(24));
        items2.Add(new ShopItem(32));

        //方法一、委托函数
        items2.Sort(sort);
        //方法二、匿名函数
        items2.Sort(delegate (ShopItem s, ShopItem s0) { return s.id - s0.id; });
        //方法三、lambda
        items2.Sort((a, b) => { return a.id - b.id; });
    }

    static int sort(ShopItem s,ShopItem s0)
    {
        return s.id - s0.id;
    }
}

Dictionary字典

Dictionary想当一种拥有泛型的HashTable,键值对类型为泛型,储存地址非连续,而且字典采用链地址法解决来哈希冲突的问题,分离链接散列表是当散列到同一个地址的值存到一个链表中。是使用一个数组加上链表来实现。

Dictionary与HashTable区别:

1、HashTable一般采用的是开放定址法(线性探测)来解决哈希冲突,固而不是按插入的顺序排序的。而Dictionary字典采用链地址法解决哈希冲突,就是将所有关键字为同义词的结点链接在同一个单链表中,所有插入顺序就是排序顺序。


2、单线程的话用Dictionary字典,有泛型优势,读取速度快,容量利用更充分。但是非线程安全,必须人为的增加lock语句进行保护,影响效率。多线程用Hashtable,因为默认哈希表支持单线程写入,多线程读取,可以调用Synchronized()方法可以获得完全线程安全的类型。
3、Dictionary字典在单线程索引数据的时候效率比较高,读取速度快,但是数据量大的时候效率会下降,毕竟是非连续地址。Hashtable哈希表的索引方式是经过散列处理的,在数据量大的时候处理效率更高,所以哈希表比较适合运用在做对象缓存,树递归算法的替代等各种需要提升效率的场合。

Synchronized()代码同步性

A对象,拥有X1和X2两个synchronized 同步代码块,那么,B线程在访问X1时,C线程也无法访问X2,需要等待B线程释放对象锁。

代码实现:

//和Hashtable有差不多的实现方法
class MyClass
{
    static void Main()
    {
        Dictionary<int, int> map = new Dictionary<int, int>();
        map.Add(1, 2);
        map.Add(2, 3);
        map[3] = 4;

        //查找(不同于Hashtable)
        //找不到会直接报错

        //遍历
        //3.键值对一起遍历
        foreach (KeyValuePair<int,int> item in map)
        {
            Console.WriteLine(item.Key +"-"+item.Value);
        }
    }
}

LinkedList

LinkedList 是泛型双向链表。

代码实现

class MyClass
{
    static void Main()
    {
        
        LinkedList<int> linkedlist=new LinkedList<int>();
        //头增
        linkedList.AddLast(10);
        //尾增
        linkedList.AddFrist(20);
        //指定位置增
        LinkedListNode<int> n=linkedList.Find(20);
        linkedList.AddAfter(n,15);
        linkedList.AddBefore(n,11);
        
        //头删
        linkedList.RemoveFirst();
        //尾删
        linkedList.RemoveLast();
        //指定删除
        linkedList.Remove(20);
        //清空
        linkedList.Clear();

        //查找
        LinkedListNode<int> node=linkedList.Find(3);
        node=linkedList.Find(20);

        //改
        node.value = 10;

        //遍历
        foreach(int item in linkedList)
        {
          Console.WriteLine(item);
        }
    }
}

委托

1、委托是函数(方法)的容器,可以理解为表示函数(方法)的变量类型,用来存储、传递函数(方法)。简单理解委托就是装载、传递函数的容器。
2、委托的本质是一个类,用来定义函数(方法)的类型(返回值和参数的类型),不同的函数(方法)必须对应和各自"格式"一致的委托。
3、系统其实已经提供了很多委托给我们用。
Action: 没有返回值,参数提供了 0~16个委托给我们用
Func<>: 有返回值,参数提供了0~16个委托给我们用

基本语法:

关键字: delegate。
语法: 访问修饰符 delegate 返回值 委托名(参数列表);
访问修饰默认不写为public,在别的命名空间中也能使用。private其它命名空间就不能用了。

代码实现:

#region 知识点三定义自定义委托
delegate void MyFun();
//除了不能重载,基本和函数一样
delegate int MyFun2(int a);
#endregion

class Test
{
    public MyFun Fun;
    public MyFun2 Fun2;

    public void TestFun(MyFun fun,MyFun2 fun2,int i)
    {
        //先处理一些别的的逻辑 当这些逻辑处理完 再执行传入的函数
        i *= 2;
        i += 2;

        fun();
        Console.WriteLine(fun2(i)); 
        //或者存进来,一会执行
        this.Fun = fun;
        this.Fun2 = fun2;
    }
}

class MyClass
{
    static void Main()
    {
        //方法一、
        MyFun f = new MyFun(Fun);
        //通过Invoke调用
        f.Invoke();

        //方法二、
        MyFun f2 = Fun;//不加括号,加括号就是调用了,直接存就行了
        f2();//直接调用

        //格式要符合
        MyFun2 f3 = Fun2;
        Console.WriteLine(f3(10));

        //类
        Test t = new Test();
        t.TestFun(Fun, Fun2, 8);

        #region 知识点四 委托变量可以存储多个函数(多播委托)
        //作用:批量处理
        MyFun ff = Fun;
        ff += Fun3;  //多加一个
        ff += Fun3;  //多加一个
        ff();
        ff -= Fun3;  //减一个
        ff -= Fun;
        ff -= Fun;  //多减不会报错
        ff();       //则调用两次这个函数
        ff = null;  //清空
        #endregion

        #region 知识点六 系统提供的委托

        //无参无返回值
        Action act = Fun;
        act += Fun3;
        act();

        //可以指定返回值类型的 泛型委托
        Func<int> fun = Fun4;

        //可以传入n个参数的 系统提供了 16个参数的委托 可以直接用
        Action<int, string> action = Fun6;

        //可以传入n个参数的 并且有返回值的 系统同时也提供了16个
        //Func<参数类型,参数类型...返回值类型>
        Func<int, int> func = Fun2;

        #endregion
    }

    static void Fun(){
        Console.WriteLine("ssss");
    }
    static void Fun3(){
        Console.WriteLine("qqqq");
    }
    static int Fun2(int value){
       return value;
    }
    static int Fun4(){
        return 4;
    }
    static void Fun6(int i,string str){
        Console.WriteLine("action<>");
    }
}

事件

1、事件是基于委托的存在。
2、事件是委托的安全包裹。
3、让委托的使用更具有安全性。
4、事件是一种特殊的变量类型。

事件的使用:

申明语法:
访问修饰符 event 委托类型 事件名;
1、事件是作为成员变量存在于类中。
2、委托怎么用事件就怎么用。

事件相对于委托的区别:

1、事件就是特殊的委托
2、事件不能在类外部直接赋值,只能使用+-添加或者减少。
3、事件不能再类外部直接调用。若要调用,就在类内部封装调用。
4、事件只能作为成员存在于类和接口以及结构体中,不能作为函数中的临时变量。

为什么有事件?

1、防止外部随意置空委托。
2、防止外部随意调用委托。
3、事件相当于对委托进行了一次封装,让其更加安全。

代码实现:
class Test
{
    public Action act;
    public event Action act2;

    public Test()
    {
        //事件的使用和委托 一模一样 只是有一些区别
        act = Fun;
        act2 = Fun;
        act();
        act2();
    }

    public void Fun(){
        Console.WriteLine("123456");
    }
    public void DoEvent(){
        act?.Invoke();     
    }
}

class MyClass
{
    static void Main()
    {
        Test t = new Test();

        //委托可以在外部赋值
        t.act = null;
        t.act = FunTest;
        //事件是不能在外部直接赋值
        //t.act2 = FunTest;
        //但是可以通过 加减 去添加移除记录的函数
        //t.act2 = t.act2 + FunTest  也不行(内部重载)
        t.act2 += FunTest;
        t.act2 -= FunTest;

        //委托是可以在外部调用的
        t.act();
        t.act.Invoke();
        //事件不可以在外部调用
        //t.act2();
        //不过可以在类的内部封装 调用
        t.DoEvent();
    }

    static void FunTest(){
        Console.WriteLine("456789");
    }
}

匿名函数

什么是匿名函数?

1、顾名思义,就是没有名字的函数。
2、匿名函数的使用主要是配合委托和事件进行使用。
3、脱离委托和事件是不会使用匿名函数的。

何时使用

1、主要是在委托传递和存储时,为了方便可以直接使用匿名函数。
2、委托或事件赋值时。
3、缺点是没有办法指定移除,因为没有记录。

代码实现:

class Test
{
    public Action action;
    //作为参数传递时
    public void DoSomeing(int a,Action fun)
    {
        Console.WriteLine(a);
        fun();
    }
    //作为返回值
    public Action GetFun()
    {
        return delegate () { Console.WriteLine("匿名函数返回"); };
    }
}

class MyClass
{
    static void Main()
    {
        #region 知识点三、使用
        //无参无返回
        Action act = delegate () {
            Console.WriteLine("无参无返回");
        };
        act();

        //有参
        Action<int,string> act1 = delegate (int a, string b) {
            Console.WriteLine(a);
            Console.WriteLine(b);
        };
        act1(10, "ss");

        //有返回值(delegate不用写返回什么类型,系统会根据返回类型自行判断)
        Func<string> F = delegate (){
            return "456";
        };
        Console.WriteLine( F());

        //一般情况会作为函数参数传递 或者 作为函数返回
        Test t = new Test();
        //有参传递
        t.DoSomeing(100, delegate () { Console.WriteLine("4.参数传递"); });
        //或者这样写
        Action ac = delegate () { Console.WriteLine("4.参数传递"); };
        t.DoSomeing(500, ac);

        //作为返回值
        Action ac2 = t.GetFun();
        ac2();
        //或者
        t.GetFun()();
        #endregion

        #region 知识点四匿名函数的缺点
        //添加到委托或事件容器中后不记录 无法单独移除
        Action ac3 = delegate () { Console.WriteLine("匿名函数四"); };
        ac3 += delegate () { Console.WriteLine("匿名函数四+1"); };
        ac3();
        //因为匿名函数没有名字所以没有办法指定移除某一个匿名函数(只能清空)
        //此匿名函数 非彼匿名函数 不能通过看逻辑是否一样就证明是一个
        // ac3 -= delegate () { Console.WriteLine("匿名函数四+1"); };  无效
        #endregion
    }
}

Lambda表达式

什么是lambda表达式?

可以将lambad表达式 理解为匿名函数的简写,除了写法不同外,使用上和匿名函数一模一样,都是和委托或者事件配合使用的。

代码实现:

class MyClass
{
    static void Main()
    {
        //1、无参无返回
        Action ac = () => { Console.WriteLine("无参数无返回"); };
        ac();
        //2、有参
        Action<int> ac1 = (int value) => { Console.WriteLine("有参数无返回{0}",value); };
        ac1(500);
        //3、甚至参数类型可以省略
        Action<int> ac2 = (value) => { Console.WriteLine("有参数无返回省略类型{0}", value); };
        ac2(800);
        //4、有返回值
        Func<string, int> f = (value) =>
        {
            Console.WriteLine("有返回值表达式" + value);
            return 10;
        };
        Console.WriteLine(f("120"));
    }
}

闭包

什么是闭包?

1、内层的函数可以引用包含在它外层的函数的变量。
2、即使外层函数的执行已经终止。
3、闭包会捕获外部函数的变量,所以即使外部函数执行完毕,闭包内的变量依然可以访问。

代码展示:

class Test
{
    public event Action act;
    public event Action act1;

    public Test()
    {
        int value = 1180;
        //这里实现了闭包
        //因为构造函数执行完成后,value的生命周期已经结束
        //但是声明的变量value生命周期改变了
        act = () => { Console.WriteLine(value); };//赋值

        //for循环后,一般来说i的生命周期已经结束了。
        for (int i = 0; i < 10; i++)
        {
            act += () => { Console.WriteLine(i); };
            //等待执行完后调用该事件只会打印10个10,因为i的最终结果是10
            //10个事件中存放的都是i,而i已经等于10

            //除非
            //那次重新开辟地址,这样每次的index都是不同的
            //10个事件中存放的都不是同一个index
            int index = i;
            act1 += () => { Console.Write(index); };
        }
    }
    //事件不能被外部调用,所有封装
    public void Do()
    {
        act();
        act1();
    }
}

协变逆变

1、协变和逆变是用来修饰泛型的,只有泛型接口和泛型委托能使用。
协变: out
逆变: in

什么是协变逆变?

1、协变:out,只能作为返回值,不能作为参数类型。
2、逆变:in,只能作为参数,不能作为返回值类型。
3、协变指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型。
4、逆变指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。

运作原理解释(个人想法):

1、协变:遵循里氏替换原则,返回输出子类,用父类装载,父类装子类。
2、逆变:遵循里氏替换原则,传入的实参是子类,形参是父类,将实参的数据传递给形参,可以解释为父类装子类。

代码展示

#region 知识点一 作用
//1.返回值 和 参数
//用out修饰的泛型 只能作为返回值 不能作为参数类型
delegate T TestOut<out T>();
//用in修饰的泛型 只能作为参数 不能作为返回值类型
delegate void TestIn<in T>(T t);

interface Fly<out T,in K>
{
    //只能作为返回值 不能作为参数类型
    public T Return();
    //只能作为参数 不能作为返回值
    public void Return(K k);
}


//2.结合里氏替换原则理解
class Father
{
}
class Son : Father
{
}

#endregion

class MyClass
{
    static void Main()
    {
        

        //知识点二、 作用
        //协变:能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型
        IEnumerable<object> list = new List<string>();
        //IEnumerable<string> list1 = new List<object>();错误
        //解释:遵循里氏替换原则,用IEnumerable<object>声明的,用List<string>
        //构造的,输出的时候是string转换成了object,本质上还是派生类到基类的转换。
        //即object = string,父类装子类

        TestOut<Son> os = () => {return new Son();};
        TestOut<Father> of = os;//解释同上
        Father f = of();

        //逆变:能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。
        Action<string> action = new Action<object>((o) => { });
        action("sss");
        //Action<object> action1 = new Action<string>((o) => { });错误
        //解释:遵循里氏替换原则,实际上调用的时候,传入的string,一样用object
        //来装载,即输入是string转换成了object,即object = string,父类装子类。
        //只是明面上像子类装父类,不和谐,称逆变。
        TestIn<Father> iF = (Father value) => { };
        TestIn<Son> iS = iF;
        iS(new Son());//解释同上
    }
}


多线程

了解线程前先了解进程

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
简单来说:
1、打开一个应用程序就是在操作系统上开启了一个进程。
2、进程之间可以相互独立运行,互不干扰。
3、进程之间也可以相互访问、操作。

什么是线程?

1、是基于进程开启的轻量级进程,是操作系统调度执行的最小单位。
2、一条线程指的是进程中一个单一顺序的控制流,就是代码从上到下运行的一条“管道”,一个进程中可以并发多个线程。

什么是多线程?

1、是一种允许计算机在同一时间执行多个程序或任务的技术。
2、简单来说就是我们可以通过代码开启新的线程,可以同时运行代码的多条“管道”就叫多线程

多线程对于我们的意义

1、可以用多线程专门处理一些复杂耗时的逻辑,比如寻路、网络通信等等。防止卡住主线程。

代码实现:

class MyClass
{
    static bool isRuning = true;
    static object obj = new object();
    static void Main()
    {
        #region 知识点四 语法相关
        //线程类 Thread
        //需要引用命名空间 using System. Threading;

        // 1.申明一个新的线程 
        // 注意线程执行的代码需要封装到一个函数中
        // 新线程将要执行的代码逻辑 被封装到了一个函数语句块中
        Thread t = new Thread(NewThreadLogic);

        // 2.启动线程 
        t.Start();

        // 3.设置为后台线程 
        //当前台线程都结束了的时候,整个程序也就结束了,即使还有后台线程正在运行
        //后台线程不会防止应用程序的进程被终止掉
        //如果不设置为后台线程可能导致进程无法正常关闭
        //原因:
        //默认开启的线程为前台线程,主线程执行完成了,新线程也还在运行(循环)
        t.IsBackground = true;

        // 4.关闭释放一个线程 
        //如果开启的线程中不是死循环 是能够结束的逻辑 那么就不用刻意的去关闭它
        // t = null;//释放变成垃圾

        //如果是死循环想 要中止这个线程 有两种方式
        //4.1-死循环中bool标识

        //Console.ReadKey();
        //isRuning = false; //关闭
        //Console.ReadKey();

        //4.2-通过线程提供的方法(注意在.Net core版本中无法中止 会报错)
        // 终止线程
        // t.Abort();

        // 5.线程休眠 
        //让线程休眠多少毫秒  1s = 1000ms
        //在哪个线程里执行 就休眠那个线程
        //Thread.Sleep(1000);  在这里写是让主线程休眠
        #endregion

        # region 知识点 五 线程之间共享数据
        //多个线程使用的内存是共享的,都属于该应用程序(进程)
        //所以要注意 当多线程 同时操作同一片内存区域时可能会出问题
        //可以通过加锁的形式避免问题

        //lock
        //当我们在多个线程当中想要访问同样的东西 进行逻辑处理时
        //为了避免不必要的逻辑执行错误
        //lock(引用类型对象(class object))

        while (true)
        {
            lock (obj) //别的地方进行,则这个等别的地方执行完 才开
            {
                //没有加锁出现的问题:
                //出现两个线程 六行代码 任意组合(比如出现 红色●) 
                Console.SetCursorPosition(0, 0);
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("■");
            }
        }
        #endregion
    }

    static void NewThreadLogic()
    {
        //新开线程 执行代码的逻辑 在该语句块中(相当于新的 Main() )
        //while (isRuning)
        //{
        //    Thread.Sleep(1000);//循环 1秒打印一次
        //    Console.WriteLine("新开线程");
        //}

        while (true)
        {
            lock (obj)
            {
                Console.SetCursorPosition(10, 5);
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("●");
            }
        }
    }
}

预处理器

什么是编译器?

1、编译器是一种翻译程序。
2、它用于将源语言程序翻译为目标语言程序。
源语言程序:某种程序设计语言写成的,比如C#、C、C++、Java等语言写的程序。
目标语言程序:二进制数表示的伪机器代码写的程序。

什么是预处理器指令?

1、预处理器指令,指导编译器在实际编译开始之前对信息进行预处理。
2、预处理器指令都是以#开始。
3、预处理器指令不是语句,所以它们不以分号;结束。
4、比如目前我们经常用到的“折叠代码块”就是预处理器指令。

其他预处理器指令

1、#define:定义一个符号,类似一个没有值的变量。
2、#undef:取消define定义的符号,让其失效。
两者都是写在脚本文件最前面,一般配合if指令使用或配合特性。
3、

#if(开头)
#elif(else if)
#else
#endif(结尾)

和if语句规则一样,一般配合#define定义的符号使用,用于高速编译器进行编译代码的流程控制。
3、

#warning
#error

告诉编译器,是报警告还是报错误,一般还是配合if使用。

有何作用

1、预处理器指令,可以让代码还没有编译之前就可以进行一些预处理判断。
2、在Unity中会用来进行一些平台或者版本的判断,决定不同的版本或者不同的平台使用不同的代码逻辑。

代码展示:

#define Unity5
#define Unity4
#define Unity2017
#define IOS

//取消其定义的符号
#undef Unity4

class MyClass
{
    static void Main()
    {
        
    #if Unity4
        Console.WriteLine("版本为Unity4");
    #elif Unity5 && IOS
        Console.WriteLine("版本为Unity5");
        //#warning 这个版本有问题
        //#error 这个版本不能用(直接报错,不让你运行)
    #else
        Console.WriteLine("其他版本");
    #endif

    }
}

反射

什么是程序集?

程序集是经由编译器编译得到的,供进一步编译执行的那个中间产物。在WINDOWS系统中,它一般表现为后缀为.dll(库文件)或者是.exe (可执行文件)的格式。
简单来说:
程序集就是我们写的一个代码集合,我们现在写的所有代码最终都会被编译器翻译为一个程序集供别人使用,比如一个代码库文件(dll)或者一个可执行文件(exe)。

什么是元数据?

有关程序以及类型的数据被称为元数据。比如说,程序中的类,类中的函数、变量等等信息就是程序的元数据,它们保存在程序集中。

什么是反射?

1、C#编写的程序会被编译成一个程序集(.DLL或.exe),其中会包含元数据、编译代码和资源,通过反射可以获取到程序集中的信息
2、通俗来讲,反射就是我们在只知道一个对象的外部而不了解内部结构的情况下,可以知道这个对象的内部实现,并且可以进行调用。

简单来说:

在程序运行时,通过反射可以得到其它程序集或者自己程序集代码的各种信息。类、函数变量、对象等等,实例化它们,执行它们,操作它们。

反射的作用

1、可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型
2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类型,以便实现某个任务时可以用到反射。
3、反射主要应用与类库,这些类库需要知道一个类型的定义,以便提供更多的功能。
4、因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性。

Type(类的信息类)

它是反射功能的基础!
1、它是访问元数据的主要方式。
2、使用Type的成员获取有关类型声明的信息。
3、有关类型的成员(如构造函数、方法、字段、属性和类的事件)。

代码展示:

1、此处代码只是作为了解其中的方法,通常不是这么用
using System. Reflection;
class Test
{
    private int i = 1;
    public int j = 2;
    public string str = "123";

    public Test(){
    }
    public Test(int i){
        this.i = i;
    }
    //this(i) 是执行上面的构造函数
    public Test(int i,string str):this(i)
    {
        this.str = str;
    }

    public void Speak()
    {
        Console.WriteLine(i);
    }
}
class MyClass
{
    static void Main()
    {
        #region 知识点五 语法相关

 #region 获取Type **************************************************
        //1.万物之父object中的GetType()可以获取对象的Type
        int a = 42;
        Type type = a.GetType();
        Console.WriteLine(type.ToString());
        Console.WriteLine(type.Name);

        //2.通过typeof关键字传入类名 也可以得到对象的Type
        Type type1 = typeof(Test);
        Console.WriteLine(type1.ToString());

        //3.通过类的名字 也可以获取类型
        // 注意:类名必须包含命名空间,不然找不到 (System.Int32)
        Type type2 = Type.GetType("System.Int32");
        Console.WriteLine(type2);

        //注意:
        //其中上面3个变量type、type1、type2指向的地址都是一样的,因为Int32也是只有一份在某个地址中。
 #endregion

 #region 得到类的程序集信息************************************************
        Console.WriteLine(type.Assembly);
        Console.WriteLine(type1.Assembly);
        Console.WriteLine(type2?.Assembly);
 #endregion

        //首先得到Type(0)
        Type t = typeof(Test);

 #region 获取类中的所有公共成员 GetMembers ***********************************
        //然后得到所有公共成员 MemberInfo(1)
        MemberInfo[] infos = t.GetMembers();
        //打印查看所有公共成员(2)
        for (int i = 0; i < infos.Length; i++){
            Console.WriteLine(infos[i]);
        }
 #endregion 

 #region 获取类的公共构造函数并调用 GetConstructors **************************
        //1、获取所有构造函数 ConstructorInfo(1)
        ConstructorInfo[] ctors = t.GetConstructors();
        //打印查看所有构造函数(2)//找到Void.ctor()就是构造函数
        for (int i = 0; i < ctors.Length; i++){
            Console.WriteLine(ctors[i]);
        }

        //2.获取其中一个构造函数并执行
        //得构造函数传入 Type数组 数组中内容按顺序是参数类型
        //执行构造函数传入 object数组 表示按顺序传入的参数

        // 2-1得到无参构造
        //无参 所以开一个0数组null
        ConstructorInfo ctor = t.GetConstructor(new Type[0]);
        //Invoke 万物之父 需要 as为相应的类
        //执行无参构造 没有参数 传入null
        Test obj = ctor?.Invoke(null) as Test;
        Console.WriteLine(obj?.j);
        //解释:
        //new一个类的时候也是相当于调用了这个类的构造函数,invoke相当于是把获取到的构造函数数据调用然后返回一个实例化对象。但是系统不知道返回的是什么对象就用obj存储


        // 2-2得到有参构造
        ConstructorInfo info = t.GetConstructor(new Type[] { typeof(int) });//Type[]{(类型)}
        obj = info?.Invoke(new object[] { 2 }) as Test;//Invoke(new object[] {(具体参数)})
        Console.WriteLine(obj?.str);

        ConstructorInfo info2 = t.GetConstructor(new Type[] { typeof(int),typeof(string) });
        obj = info2?.Invoke(new object[] { 8,"456" }) as Test;
        Console.WriteLine(obj?.str);
 #endregion

 #region 获取类的公共成员变量 GetFields *************************************
        //1.得到所有成员变量 FieldInfo
        FieldInfo[] fields = t.GetFields();
        //2、打印查看所有公共成员变量
        for (int i = 0; i < fields.Length; i++){
            Console.WriteLine(fields[i]);
        }
        //3、得到 指定名称 的公共成员变量
        FieldInfo field = t.GetField("j");//j根据上面遍历可以知道
        Console.WriteLine(field?.Name);
        Console.WriteLine(field?.FieldType.Name);

        //4.通过反射获取和设置对象的值,一般是使用不了Test的,这里只是演示
        Test test = new Test();
        //test.j = 88;
        test.str = "33333";

        //4-1通过反射 获取对象的某个变量的值
        Console.WriteLine(field?.GetValue(test));
        //4-2通过反射 设置指定对象的某个变量的值
        field?.SetValue(test, 99);
        Console.WriteLine(field?.GetValue(test));
 #endregion

 #region 获取类的公共成员方法 GetMethod *************************************
        //MethodInfo 是方法的反射信息,这里用string举例子
        Type strType = typeof(string);
        MethodInfo[] method = strType.GetMethods();
        for (int i = 0; i < method.Length; i++){
            Console.WriteLine(method[i]);
        }

        //指定函数成员
        //1.如果存在方法重载 用Type数组表示参数类型
        MethodInfo info1 = strType.GetMethod("Substring",
            new Type[] { typeof(int), typeof(int) });

        MethodInfo Info2 = strType.GetMethod("Format",
           new Type[] { typeof(string), typeof(object) , typeof(object) });

        //2.调用该方法
        //注意:如果是静态方法Invoke中的第一个参数传null即可
        //没有参数 传null即可
        string str = "abcdefg";
        //第一个参数 相当于 是哪个对象要执行这个成员方法
        //简单说就是 str.Substring(2,3)
        object result = info1?.Invoke(str, new object[] { 2, 3 });
        Console.WriteLine(result);

        //静态方法,就不需要实例化对象(第一个参数)
        Console.WriteLine(Info2?.Invoke(null, new object[] { "hh{0}{1}", 12, 10 }));
 #endregion
其他
        #region 其它
        //Type;
        //得枚举
        //GetEnumName
        //GetEnumNames

        //得事件
        //GetEvent
        //GetEvents

        //得接口
        //GetInterface
        //GetInterfaces

        //得属性
        //GetProperty
        //GetPropertys
        //等等
        #endregion
2、实际中的用法

Activator:
用于快速实例化对象的类,用于将Type对象快捷实例化为对象。

        #region Activator
        //先得到Type1,然后快速实例化一个对象
        Type test1 = typeof(Test);

        //1.无参构造
        Test te = Activator.CreateInstance(test1) as Test;
        Console.WriteLine(te?.str);
        //2.有参数构造
        te = Activator.CreateInstance(test1,77) as Test;
        Console.WriteLine(te?.j);

        te = Activator.CreateInstance(test1, 88,"147852") as Test;
        Console.WriteLine(te?.str);
        #endregion

Assembly:
1、程序集类,主要用来加载其它程序集,加载后才能用Type来使用其它程序集中的信息。
2、如果想要使用不是自己程序集中的内容,需要先加载程序集,比如 dl1文件(库文件)。
3、简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。

#if icon
        #region Assembly
        //三种加载程序集的函数
        //一般用来加载在同一文件下的其它程序集
        //Assembly asembly2 = Assembly. Load ("程序集名称");

        //一般用来加载不在同一文件下的其它程序集
        //Assembly asembly = Assembly. LoadFrom("包含程序集清单的文件的名称或路径");
        //Assembly asembly3 = Assembly. LoadFile("要加载的文件的完全限定路径");

        //1.先加载一个指定程序集
        Assembly assembly = Assembly.LoadFrom(@"该程序集.dll文件的路径");//@取消'\'

        //2、得到里面所有类型
        Type[] types = assembly.GetTypes();

        //3、查看里面的类型信息,获取程序集中所有类名
        for (int i = 0; i < types.Length; i++) 
        {
            Console.WriteLine(types[i]);
        }

        //4、加载程序集中的一个指定类对象的类型,之后才能使用反射(通过步骤3知道所有类名)
        Type icon = assembly.GetType("命名空间.类名字");

        //5、获取这个类里面所有公共成员
        MemberInfo[] members = icon.GetMembers();

        //6、查看这个类中的所有方法
        for (int i = 0; i < members.Length; i++)
        {
            Console.WriteLine(members[i]);
        }

        //7、动态实例化该类对象,即调用该类构造函数
        //7-1,这里解释一个,如果调用一个有参构造函数,而参数其中一个是该程序集中的枚举,该怎么办。
        //7-2,首先得到,枚举类型type 来得到可以传入的参数(枚举名上面3可知)
        Type type3 = assembly.GetType("命名空间.枚举名字");
        //7-3,获取里面的枚举变量名,打印出来看
        //FieldInfo[] fields3 = type3 .GetFields();
        //7-4,得到这个枚举类中的一个成员(名字通过7-3得到)
        FieldInfo right = type3.GetField("Right");

        //8、实例化对象,不知道什么类型,通过 object 来装
        //传入的是枚举的值(没有对象这个概念设置值,所以传null),不是right成员
        object iconObj = Activator.CreateInstance(icon,10,5,right.GetValue(null));

        //9、得到对象中的方法,通过反射(方法名字已经在步骤6了解到)
        MethodInfo move = icon.GetMethod("函数名字");
        MethodInfo draw = icon.GetMethod("函数名字");
        MethodInfo clear = icon.GetMethod("函数名字");

        while (true)
        {
            Thread.Sleep(1000);
            //10、谁调用传谁,上面的实例化对象icobObj调用它的方法
            //比如,Test t = new Teat();t.move();
            clear.Invoke(iconObj, null);
            move.Invoke(iconObj, null);
            draw.Invoke(iconObj, null);
            //如果有参数,这方法需要传入什么参数,在步骤6可以了解到
            Draw.Invoke(iconObj, new object[] { 2, "str" });

        }
        #endregion
#endif

特性

特性是什么?

1、特性是一种允许我们向程序的程序集添加元数据的语言结构,它是用于保存程序结构信息的某种特殊类型的类。
2、特性是一种用于在程序运行时传递各种元素(例如类、方法、结构、枚举等)行为信息的声明性代码。
简单来说:
1、特性本质是个类,我们可以利用特性类为元数据添加额外信息。
2、比如一个类、成员变量、成员方法等等为他们添加更多的额外信息,之后可以通过反射来获取这些额外信息。

自定义特性

//继承特性基类 Attribute

class MyCustomAttribute : Attribute
{
    public string info;

    public MyCustomAttribute(string info)
    {
        this.info = info;
    }

    public void TestFun()
    {
        Console.WriteLine("特性的方法");
    }
}

特性的使用

[特性名(参数列表)]
1、本质上就是在调用特性类的构造函数。
2、类名后面的Attribute会自动省略

[MyCustom("这个是一个计算类")]
class My
{
    [MyCustom("这是一个成员变量")]
    public int value;

    [MyCustom("这是一个计算加法函数")]
    public void TestFun([MyCustom("函数参数")]int a)
    {
    }
}

限制自定义特性的使用范围

通过为特性类 加特性 限制其使用范围

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true,Inherited = true)]
//参数-: AttributeTargets - 特性能够用在哪些地方
//参数二: AllowMultiple - 是否允许多个特性实例用在同一个目标上
//参数三: Inherited -  特性是否能被派生类和重写成员继承
public class MyCustomAttirbute : Attribute
{
}

系统自带特性

过时特性

过时特性:Obsolete
1、用于提示用户使用的方法等成员已经过时,建议使用新方法。一般加在函数前的特性。

调用者信息特性

哪个文件调用:CallerFilePath特性
哪一行调用:CallerLineNumber特性
哪个函数调用:CallerMemberName特性
用法:
1、需要引用命名空间 using System. Runtime. CompilerServices;
2、一般作为函数参数的特性
3、一般用来报错 比如用 try{ }catch{ 这里使用,找出错误地方 }

class TestMyClass
{
    //参数一:提示内容
    //参数二:true-使用该方法时会报错 false-使用该方法会警告
    [Obsolete("OldSpeak方法已经过时了,请使用Speak方法",false)]
    public void OidSpeak(string str)
    {
        Console.WriteLine(str);
    }
    public void Speak()
    {
    }

    //给默认值,如果传参的话,那就没用了Caller
    public void SpeakCaller(string str, [CallerFilePath]string fileName = "",
        [CallerLineNumber]int lineNumber = 0, [CallerMemberName]string target = "")
    {
        Console.WriteLine(str);
        Console.WriteLine(fileName);
        Console.WriteLine(lineNumber);
        Console.WriteLine(target);
    }

}
条件编译特性

条件编译特性:Conditional
用法:
1、它会和预处理指令 #define配合使用
2、需要引用命名空间using System.Diagnostics;
3、主要可以用在一些调试代码上,有时想执行有时不想执行的代码

外部Dll包函数特性

外部Dll包函数特性:DllImport
用法:
1、用来标记非.Net(C#)的函数,表明该函数在一个外部的DLL中定义。
2、一般用来调用c或者C++的D11包写好的方法。
3、需要引用命名空间 using System.Runtime.InteropServices。

class MyClass
{
    //外部Dll包函数特性
    [DllImport(@"D:\大一\各种代码\c++和数据结构\c++测试\演讲比赛系统\x64")]
    private static extern int main();

    //只有 #define Unity8  才能被执行
    [Conditional("Unity8")]
    static void Fun()
    {
        Console.WriteLine("Fun执行");
    }
    static void Main()
    {
        //外部Dll包函数特性
        //main();

        #region 特性的使用
        My mc = new My();
        Type t = mc.GetType();
        // t = typeof(My);
        // t = Type.GetType("My");

        //判断是否使用了某个特性
        //参数一:特性的类型
        //参数二:代表是否搜索继承链(属性和事件忽略这个参数)
        if (t.IsDefined(typeof(MyCustomAttribute),false))
        {
            Console.WriteLine("该类型应用了MyCustom特性");
        }

        //获取Type元数据中的所有特性
        object[] arr = t.GetCustomAttributes(false);
        for (int i = 0; i < arr.Length; i++)
        {
            if (arr[i] is MyCustomAttribute)
            {
                Console.WriteLine((arr[i] as MyCustomAttribute).info);
                (arr[i] as MyCustomAttribute).TestFun();
            }
           
        }
        #endregion

        //过时特性
        TestMyClass tc = new TestMyClass();
        tc.OidSpeak("456");
        tc.Speak();

        //调用者信息特性
        tc.SpeakCaller("78+");

        //条件编译特性
        Fun();
    }
}

迭代器

迭代器是什么?

1、迭代器(iterator) 有时又称光标(cursor)。
2、是程序设计的软件设计模式。
3、选代器模式提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的标识。

为什么要使用迭代器?

考虑一下,如果要用for遍历一个字典,怎么办?没有办法,因为我们根本完全不知道内部结构,没法给出遍历逻辑。所有就需要迭代器foreach来进行遍历操作。

标准选代器的实现方法

1、关键接口: IEnumerator,Enumerable
2、命名空间: using System.Collections;
3、可以通过同时继承 IEnumerable 和 IEnumerator 实现其中的方法。

foreach本质

1、先获取in后面这个对象的IEnumerator,通过调用对象其中的 GetEnumerator 方法返回的迭代器获取。
2、执行得到这个IEnumerator对象中的MoveNext方法。
3、只要MoveNext方法的返回值时true 就会去得到Current 然后复制给item。
4、相当于while,重复2、3步骤...直到MoveNext方法的返回false。

 CustomList list = new CustomList();
 foreach (int item in list)
 {
     Console.WriteLine(item);
 }
代码展示:
class CustomList : IEnumerable,IEnumerator
{
    private int[] list;
    private int position = -1;

    public CustomList()
    {
        list = new int[] {2,3,45,5,6,7,8,9};
    }

    #region IEnumerable
    //需要返回一个迭代器(1)
    public IEnumerator GetEnumerator()
    {
        Reset();//用于第一次重置光标
        return this;
    }
    #endregion

    //这里定义集合的遍历逻辑,应该返回什么内容
    //返回值 只有在movNext返回的是true的时候进行调用(3)
    public object Current
    { 
        get {
            //逻辑
            //Switch(){}
            return list[position];
        }
    }

    //移动光标(2)
    public bool MoveNext()
    {
        position++;
        return position < list.Length;
    }

    //让光标重置(重新开始)
    public void Reset()
    {
        position = -1;
    }
}

用yield return 语法糖实现选代器

1、yield return 是C#提供给我们的语法糖。
2、主要作用就是将复杂逻辑简单化,可以增加程序的可读性,从而减少程序代码出错的机会。
用法:
1、关键接口: IEnumerable
2、命名空间: using System.Collections;
3、让想要通过foreach遍历的自定义类实现接口中的方法GetEnumerator即可

class CustomList2 : IEnumerable
{
    private int[] list;

    public CustomList2()
    {
        list = new int[] { 21, 53, 45, 55, 46, 87, 98, 79 };
    }

    public IEnumerator GetEnumerator()
    {
       for (int i = 0; i < list.Length; i++)
       {
            //yield关键字 配合选代器使用
            //可以理解为暂时返回保留当前的状态,一会还会在回来
            //C#的语法糖(系统帮你实现上面写的代码)
            yield return list[i];
       }
    }
}

用yield return 语法糖为泛型类实现选代器

class CustomList<T> : IEnumerable
{
    private T[] list;

    public CustomList(params T[] values)
    {
        //数组赋值
        this.list = values;
    }

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < list.Length; i++)
        {
            yield return list[i];
        }
    }
}
class MyClass
{
    static void Main()
    {
        CustomList2 list2 = new CustomList2();
        foreach (int item in list2)
        {
            Console.WriteLine(item);
        }

        CustomList<int> list3 = new CustomList<int>(1,2,3,4,5,6,7,8,9);
        foreach (int item in list3)
        {
            Console.WriteLine(item);
        }
    }
}

unity中协程

1、协程,就是一个迭代器的扩展。
2、协程(Coroutine)是一种轻量级的并发编程技术,它可以在单个线程内实现多个执行流的并发操作。协程的原理主要涉及到两个概念:上下文切换和yield (让出执行权)。
上下文切换:协程通过上下文切换来实现在不同协程之间的切换执行。当一个协程执行到某个位置时,它会保存当前的上下文(包括程序计数器、堆栈指针等信息),然后切换到下一个要执行的协程,恢复该协程保存的上下文,并从切换位置继续执行。
yield (让出执行权) : yield是协程中的一个关键操作,它可以让协程主动让出执行权,将控制权交给其他协程。当一个协程执行到 yield 语句时,它会进行挂起操作,并将结果返回给调度器(或其他协程),同时保存当前的上下文。接下来,调度器会选择另一个协程来执行,将其保存的上下文恢复,然后继续执行。


特殊语法

class Person
{
    private int momey;
    public bool sex;

    public string Name
    {
        get => "smg";
        set => sex = false;
    }
    public int Age
    {
        get;
        set;
    }

    public Person(int momey)
    {
        this.momey = momey;
    }

    //一句代码可以这么用 返回 调用
    public int add(int x, int y) => x + y;

    public void Speak(string str) => Console.WriteLine(str);

}

一、var隐式类型

var是一种特殊的变量类型,它可以用来表示任意类型的变量。
注意:
1、var不能作为类的成员 只能用于临时变量申明,也就是一般写在函数语句块中。
2、var必须初始化。
3、不确定类型时 使用。

 var i = 5;
        var s = "adw";
        var arr = new string[] {"wda","dw","wa"};
        var list = new List<string>();

二、设置对象初始值

1、申明对象时,可以通过直接写大括号的形式初始化公共成员变量和属性。

        Person p = new Person(54) { Age = 18, Name = "opq", sex = true };
        Person p2 = new Person(10) { Name = "pp" };
        Console.WriteLine(p2.Name);
        Console.WriteLine(p.Age);

三、设置集合初始值

1、申明集合对象时,也可以通过大括号直接初始化内部属性。

        int[] arr1 = new int[] {1,2,3,4,5};

        List<int> list2 = new List<int>() { 1,2,3,4,5,6};

        List<Person> list3 = new List<Person>()
        {
            p,p2,
            new Person(14){Age = 18},
            new Person(19){Age = 18,Name = "www"}
        };

        Dictionary<int, int> list4 = new Dictionary<int, int>()
        {
            {1,1},{2,789}
        };

四、匿名类型

        //var 变量可以声明为自定义的匿名类型(临时类)
        var v = new { age = 10, money = 11, name = "11" };//里面只能有成员变量
        Console.WriteLine(v.age);

五、可空类型

        //1.值类型是不能赋值为 空的
        // int c = null;
        //2.申明时 在值类型后面加? 可以赋值为空
        int? c = 8;
        //3.判断是否为空
        if (c.HasValue)
        {
            Console.WriteLine(c.Value);
            Console.WriteLine(c);//一样的
        }
        //4.安全获取可空类型值
        int? d = null;
        // 4-1.如果为空默认返回值类型的默认值0 不为空返回本身值
        Console.WriteLine(d.GetValueOrDefault());
        // 4-2.也可以指定一个默认值 这个默认值并不会赋值给变量 变量不变
        Console.WriteLine(d.GetValueOrDefault(15));

        object o = null;
        if (o!=null)//空是不能调用方法的
        {
            o.ToString();
        }
        // 相当于是一种语法糖 能够帮助我们自动去判断o是否为空
        // 如果是null就不会执行tosring也不会报错
        o?.ToString();

        int[] ar = null;
        Console.WriteLine(ar?[0]);//不会报错

        Action ac = null;
        if (ac != null)
        {
            ac.Invoke();
        }
        ac?.Invoke();//不会报错

六、空合并操作符

1、空合并操作符 ??
2、左边值 ?? 右边值。
3、如果左边值为null,就返回右边值,否则返回左边值。
4、只要是可以为 null的类型都能用。

        int? intv = null;
        int intI = intv == null ? 10 : intv.Value;
        Console.WriteLine(intI);

        int inI = intv ?? 100;
        Console.WriteLine(inI);

        string str = null;
        str = str ?? "sssss";
        Console.WriteLine(str);

七、内插字符串

1、关键符号: $
2、用$来构造字符串,让字符串中可以拼接变量。

        string name = "abcdefg";
        int age = 123;
        Console.WriteLine($"hhhh:{name},年龄:{age}");

热门相关:极品仙医在都市   田园晚色:肥妇三嫁良夫   伏天剑尊   学霸,你女朋友掉了   弃妇种田忙