• C#进阶


    C#进阶

    简单数据结构类

    Arraylist

    本质:Arraylist是一个C#为我们封装好的类,它的本质是一个object类型的数组,Arraylist类帮助我们实现很多方法:数组的增删查改等等。

    //申明
    using System.Collections;
    //需要引用命名空间using System.Collections;
    ArrayList array = new ArrayList();
    //1.增(因为是object类型,所以可以存任何类型的数据)
    array.Add(1);
    array.Add("123");
    array.Add(true);
    array.Add(new object());
    array.Add(new Test());
    
    //范围增加(批量增加,把另一个list容器里面的内容加到后面)
    ArrayList array2 = new ArrayList();
    array2.Add(123);
    array.AddRange(array2);
    
    //增加到指定位置
    array2.Insert(1,"123456");
    Console.WriteLine(array[1]);
    
    //2.删
    //移除指定元素 从头找 找到删
    array.Remove(1);
    //移除指定位置的元素
    array.Remove(2); //删object
    //清空
    array.Clear();
    
    //3.查
    //得到指定位置的元素
    Console.WriteLine(array[0]);
    //查看元素是否存在
    if(array.Contains("123"))
    {
        Console.WriteLine("存在123");
    }
    //正向查找元素位置 找到返回值是位置 找不到返回-1
    int index = array.IndexOf(true);
    Console.WriteLine(index); //1
    Console.WriteLine(array.IndexOf(false)); //-1
    //反向查找LastIndexOf
    
    //4.改
    Console.WriteLine(array[0]);
    array[0] = "999";
    Console.WriteLine(array[0]);
    
    
    //遍历
    //长度
    Console.WriteLine(array.Count); //7
    //容量
    Console.WriteLine(array.Capacity); //16
    for(int i = 0; i < array.Count; i++)
    {
        Console.WriteLine(array[i]);
    }
    //迭代器遍历
    foreach (object item in array)
    {
        Console.WriteLine(item);
    }
    

    装箱拆箱

    • ArrayList本质上是一个可以自动扩容的object数组,由于用万物之父来储存数据,自然存在装箱拆箱。

    • 当其中进行值类型存储时就是在装箱,当将值类型对象取出来转换使用时,就存在拆箱。

      int i = 1;
      array[0] = i; //装箱
      i = (int)array[0]; //拆箱
      

    所以ArrayList尽量少用,之后会学习更好的数据容器。

    但也不是不能用,ArrayList的优点是object可以存储任何类型的数据。

    Stack

    Stack也是一个c#为我们封装好的类,它的本质也是object[ ]数组,只是封装了特殊的存储规则。

    Stack是栈储存容器,栈是一种先进后出的数据结构。

    //申明
    using System.Collections;
    //需要引用命名空间using System.Collections;
    Stack stack = new Stack();
    
    
    //增删查改
    //压栈
    stack.Push(1);
    stack.Push(true);
    stack.Push(1.2f);
    stack.Push(new Test());
    
    //栈中不存在删除的概念,只有取
    //弹栈(取)
    object v = stack.Pop();
    Console.WriteLine(v); //Test里的东西
    object v = stack.Pop();
    Console.WriteLine(v); //1.2f
    
    //查
    //栈无法查看指定元素的内容,只能看栈顶的内容
    object v = stack.Peek();
    Console.WriteLine(v); //true
    object v = stack.Peek();
    Console.WriteLine(v); //true
    //查看元素是否存在于栈中
    if(stack.Contains("123"))
    {
        Console.WriteLine("存在123");
    }
    
    //改
    //栈无法改变其中的元素 只能压栈(存) 弹栈(取)
    //实在要改 只有清空
    stack.Clear();
    
    stack.Push("1");
    stack.Push(2);
    stack.Push("哈哈哈");
    
    
    //遍历
    //长度
    Console.WriteLine(stack.Count);
    //用foreach遍历,而且遍历出来的顺序也是从栈顶到栈底
    foreach (object item in stack)
    {
        Console.WriteLine(item);
    }
    //不可以直接用for循环遍历,需要将栈转化为object数组 
    //遍历出来的顺序也是从栈顶到栈底
    object[] array = stack.ToArray();
    for(int i = 0;i < array.Length;i++)
    {
        Console.WriteLine(array[i]);
    }
    //循环弹栈 即边取边用
    Console.WriteLine(stack.Count); //3
    while(stack.Count > 0)
    {
        object o = stack.Pop();
        Console.WriteLine(o);
    }
    Console.WriteLine(stack.Count); //0
    

    装箱拆箱

    • ArrayList本质上是一个可以自动扩容的object数组,由于用万物之父来储存数据,自然存在装箱拆箱。

    • 当其中进行值类型存储时就是在装箱,当将值类型对象取出来转换使用时,就存在拆箱。

    Queue

    • Queue也是一个c#为我们封装好的类,它的本质也是object[ ]数组,只是封装了特殊的存储规则。

    • Queue是队列储存容器,栈是一种先进先出的数据结构。

    //和栈基本一样
    Queue queue = new Queue();
    
    queue.Enqueue(1);
    Queue.Enqueue("123");
    Queue.Enqueue(1.4f);
    Queue.Enqueue(new Test());
    
    object v = queue.Dequeue();
    Console.WriteLine(v);
    
    v = queue.Peek();
    Console.WriteLine(v);
    
    queue.Clear();
    queue.Enqueue(1);
    queue.Enqueue(2);
    queue.Enqueue(3);
    
    Console.WriteLine(queue.Count);
    
    foreach (object item in queue)
    {
        Console.WriteLine(item);
    }
    
    object[] array = queue.ToArray();
    for(int i = 0;i < array.Length;i++)
    {
        Console.WriteLine(array[i]);
    }
    
    Console.WriteLine(queue.Count); //3
    while(queue.Count > 0)
    {
        object o = queue.Pop();
        Console.WriteLine(o);
    }
    Console.WriteLine(queue.Count); //0
    

    装箱拆箱

    • ArrayList本质上是一个可以自动扩容的object数组,由于用万物之父来储存数据,自然存在装箱拆箱。

    • 当其中进行值类型存储时就是在装箱,当将值类型对象取出来转换使用时,就存在拆箱。

    Hashtable

    • Hashtable是基于键的哈希代码组织起来的键、值对
    • 它的在就主要作用是提高数据查询的效率
    • 使用键来访问集合中的元素
    //申明
    //需要引用命名空间using System.Collections;
    Hashtable hashtable = new Hashtable();
    
    
    //增删查改
    //增 注意不能出现相同的键
    hashtable.Add(1,"123");
    hashtable.Add("123",2);
    hashtable.Add(true,false);
    hashtable.Add(false,true);
    
    //删 只能通过键去删除
    hashtable.Remove(1);
    //删除不存在的键没反应
    hashtable.Remove(2);
    //直接清空
    hashtable.Clear();
    hashtable.Add(1,"123");
    hashtable.Add(2,"1234");
    hashtable.Add(3,"123");
    hashtable.Add("123",12);
    
    //查
    //1.通过键值查找 找不到会返回空
    Console.WriteLine(hashtable[1]);
    Console.WriteLine(hashtable[2]); //null
    Console.WriteLine(hashtable["123"]);
    //2.查看是否存在 
    //根据键检测
    if(hashtable.Contains(2))
    {
        Console.WriteLine("存在键为2的键值对");
    }
    if(hashtable.ContainsKey(2))
    {
        Console.WriteLine("存在键为2的键值对");
    }
    //根据值检测
    if(hashtable.ContainsValue(12))
    {
        Console.WriteLine("存在值为12的键值对");
    }
    
    //改 只能修改键对应值的内容,无法修改键
    Console.WriteLine(hashtable[1]);
    hashtable[1] = 100.5f;
    Console.WriteLine(hashtable[1]);
    
    
    //遍历
    //得到键值对 对数
    Console.WriteLine(hashtable.Count);
    //1.遍历所有键
    foreach (object item in hashtable.Key)
    {
        Console.WriteLine("键:" + item);
        Console.WriteLine("值:" + hashtable[item]);
    }
    //2.遍历所有值
    foreach (object item in hashtable.Values)
    {
        Console.WriteLine("值:" + item);
    }
    //3.键值对一起遍历
    foreach (DictionaryEntry item in hashtable.Key)
    {
        Console.WriteLine("键:" + item.Key);
        Console.WriteLine("值:" + item.Value);
    }
    //4.迭代器遍历法
    IDictionaryEnumerator myEnumerator = hashtable.GetEnumerator();
    bool flag = myEnumerator.MoveNext();
    while(flag)
    {
        Console.WriteLine("键:" + myEnumerator.Key + "值:" + myEnumerator.Value);
        flag = myEnumerator.MoveNext();
    }
    

    泛型

    泛型

    概念:

    • 泛型实现了类型参数化,达到代码重用的目的,通过类型参数化来实现同一份代码上操作多种类型
    • 泛型相当于类型占位符,定义类或方法时使用替代符代表变量类型,当真正使用类或者方法时再具体指定类型
    //泛型分类:
    //1.泛型类和泛型接口
    //class 类名<泛型占位字母>
    class TestClass<T>
    {
        public T value;
    }
    //泛型占位符可以有多个
    class TestClass2<T1,T2,K,LL,M>
    {
        public T1 value1;
        public T3 value2;
        public K value3;
        public LL value4;
        public M value5;
    }
    //interface 接口名<泛型占位字母>
    interface TestInterface<T>
    {
        T value
        {
            get;
            set;
        }
    }
    class Test:TestInterface<int>
    {
        public int value
        {
            get;
            set;
        }
    }
    //2.泛型函数
    //函数名<泛型占位字母>(参数列表)
    //2.1普通类中的泛型方法
    class Test2 
    {
        public void Testfun<T>(T value)
        {
            Console.WriteLine(value);
        }
        public void Testfun<T>()
        {
            //用泛型在里面做一些逻辑处理
            T t = default(T); //不可以赋值0或null之类,用default能获得类型默认值
        }
        public void Testfun<T>(string v)
        {
            return default(T);
        }
        public void Testfun<T,K,M>(T t,K k,M m)
        {
            
        }
    }
    //2.2泛型类中的泛型方法
    class Test2<T>
    {
        //不是泛型方法 T是类中定义的
        /*public void Testfun<T>(T value)
        {
            Console.WriteLine(value);
        }*/
        public void Testfun<K>(K k)
        {
            Console.WriteLine(k);
        }
    }
    
    TestClass<int> t = new TestClass<int>();
    t.value = 10;
    Console.WriteLine(t.value);
    
    TestClass<string> t = new TestClass<string>();
    t.value = "123123";
    Console.WriteLine(t.value);
    
    class TestClass2<int,string,float,TestClass<int>,uint> = new TestClass2<int,string,float,TestClass<int>,uint>();
    
    Test2 tt = new Test2();
    tt.TestFun<string> ("123123"); //输出123123
    

    泛型约束

    概念:让泛型的类型有一定的限制

    关键字:where

    泛型约束一共有六种:

    • 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>() where K:struct
        {
            
        }
    }
    //引用是不可用null值的数据类型,必须是值类型
    Test1<int> t1 = new Test1<int>();
    t1.TestFun<float>(1.3f);
    
    //2.引用类型约束
    class Test2<T> where T:class
    {
        public T value;
        public void TestFun<K>() where K:class
        {}
    }
    Test2<Random> t2 = new Test2<Random>();
    t2.value = new Ramdom();
    t2.TestFun<object>(new object());
    
    //3.公共无参构造约束
    class Test3<T> where T:new()
    {
        public T value;
        public void TestFun<K>(K k) where K:new()
        {
            
        }
    }
    class Test1
    {
        //默认有无参构造函数
    }
    class Test2
    {
        public Test2(int a);
    }
    class Test3
    {
        private Test3();
    }
    Test3<Test1> t3 = new Test3<Test1>();
    //Test3<Test2> t3 = new Test3<Test2>(); //会报错 有参构造函数会把无参构造函数顶掉
    //Test3<Test3> t3 = new Test3<Test3>(); //会报错 必须公共无参构造函数
    //还需要非抽象类 因为抽象类无法被new
    
    //4.类约束
    class Test4<T> where T:Test1
    {
        public T value;
        public void TestFun<K>(K k) where K:Test1
        {
            
        }
    }
    class Test3 : Test1
    {
        
    }
    Test4<Test1> t4 = new Test3<Test1>();
    Test4<Test3> t4 = new Test3<Test3>();
    //Test4<Test2> t4 = new Test3<Test2>(); //会报错 不是Test1或其派生类 父类也不行
    
    //5.接口约束
    interface IFly
    {
        
    }
    interface Test5 : IFly
    {
        
    }
    class Test5<T> where T : IFly
    {
        public T value;
        public void TestFun<K>(K k) where K : IFly
        {
            
        }
    }
    Test5<IFly> t5 = new Test5<IFly>();
    //不能new IFly 但可以用里氏替换原则
    t5.value = new Test5();
    //也可以直接Test5<Test5> t5 = new Test5<Test5>();
    
    //6.另一个泛型约束
    class Test6<T,U> where T : U
    {
        public T value;
        public void TestFun<K>(K k) where K : U
        {
            
        }
    }
    Test6<Test5,IFly> t6 = new Test6<Test5,IFly>();
    Test6<Test5,Test5> t6 = new Test6<Test5,Test5>();
    
    
    //约束的组合使用
    class Test7<T> where T: class,IFly
    {
        
    }
    
    
    //多个泛型有约束
    class Test8<T,K> where T:class,new() where K:struct
    {
        
    }
    

    常用泛型数据结构类

    List

    概念:List是一个C#为我们封装好的类,它的本质是一个可变类型的泛型数组,List类实现了很多方法如:泛型数组的增删查改。

    //申明
    //using System.Collections.Generic
    List<int> list = new List<int>();
    List<string> list2 = new List<string>();
    List<bool> list3 = new List<bool>();
    
    //增删查改
    //增
    list.Add(1);
    list.Add(2);
    list.Add(3);
    list.Add(4);
    
    list2.Add("123");
    
    List<string> listStr = new List<string>();
    ListStr.Add("123");
    list.AddRange(listStr);
    
    //删
    //1.移除指定元素
    list.Remove(1);
    //2.移除指定位置的元素
    list.RemoveAt(0);
    //3.清空
    list.clear();
    
    list.Add(1);
    list.Add(2);
    list.Add(3);
    list.Add(4);
    
    //查
    //1.得到指定位置的元素
    Console.WriteLine(list[0]);
    //2.查看元素是否存在
    if(list.Contains(1))
    {
        Console.WriteLine(1);
    }
    //3.正向查找元素位置 找到返回值是位置 找不到返回-1
    int index = list.IndexOf(1);
    Console.WriteLine(index); //0
    Console.WriteLine(array.IndexOf(5)); //-1
    //4.反向查找LastIndexOf
    int index = list.IndexOf(2);
    Console.WriteLine(index); //1
    
    //4.改
    Console.WriteLine(list[0]); //1
    list[0] = 99;
    Console.WriteLine(list[0]); //99
    
    //遍历
    //长度
    Console.WriteLine(list.Count); //4
    //容量
    Console.WriteLine(array.Capacity); //8 
    for(int i = 0; i < list.Count; i++)
    {
        Console.WriteLine(list[i]);
    }
    //迭代器遍历
    foreach (object item in list)
    {
        Console.WriteLine(item);
    }
    

    Dictionary

    和hashtable基本一样,可以理解为拥有泛型的hashtable,它也是基于键的哈希代码组织起来的,键、值对。

    键值对类型从Hashtable的object变为了可以自己制定类型的泛型。

    //申明
    //using System.Collection.Generic
    Dictionart<int,string> dictionary = new Dictionary<int,string>();
    
    //增删查改
    //增
    dictionary.Add(1,"123");
    dictionary.Add(2,"222");
    dictionary.Add(3,"222");
    
    //1.删 只能通过键去删除
    dictionary.Remove(1);
    //删除不存在的键没反应
    dictionary.Remove(4);
    //2.直接清空
    dictionary.Clear();
    
    dictionary.Add(1,"123");
    dictionary.Add(2,"222");
    dictionary.Add(3,"222");
    
    //查
    //1.通过键值查找 找不到会直接报错
    Console.WriteLine(dictionary[1]);
    //Console.WriteLine(dictionary[4]); //会报错
    
    //2.查看是否存在 找不到就返回false
    //根据键检测 和哈希表不一样只有一个函数
    if(dictionary.ContainsKey(2))
    {
        Console.WriteLine("存在键为2的键值对");
    }
    //根据值检测
    if(dictionary.ContainsValue(123))
    {
        Console.WriteLine("存在值为123的键值对");
    }
    
    //改 只能修改键对应值的内容,无法修改键
    Console.WriteLine(dictionary[1]);
    dictionary[1] = "555";
    Console.WriteLine(dictionary[1]);
    
    
    //遍历
    //得到键值对 对数
    Console.WriteLine(dictionary.Count);
    //1.遍历所有键
    foreach (int item in dictionary.Key)
    {
        Console.WriteLine("键:" + item);
        Console.WriteLine("值:" + dictionary[item]);
    }
    //2.遍历所有值
    foreach (string item in dictionary.Values)
    {
        Console.WriteLine("值:" + item);
    }
    //3.键值对一起遍历
    foreach (KeyValue<int,string> item in dictionary)
    {
        Console.WriteLine("键:" + item.Key);
        Console.WriteLine("值:" + item.Value);
    }
    
    

    顺序储存和链式储存

    顺序存储:用一组地址连续的存储单元一次存储线性表的各个数据元素。

    链式存储:用一组任意的存储单元存储线性表的各个数据元素。

    从增删查改的角度思考顺序存储和链式存储的优缺点:

    • 增:链式存储 计算上 优于顺序存储 (中间插入时链式不用像顺序一样去移动位置)

    • 删:链式存储 计算上 优于顺序存储 (中间删除时链式不用像顺序一样去移动位置)

    • 查:顺序存储 使用上 优于链式存储 (数组可以直接通过下标得到元素,链式需要遍历)

    • 改:顺序存储 使用上 优于链式存储 (数组可以直接通过下标得到元素,链式需要遍历)

    Linkedlist

    Linkedlist是一个c#为哦我们封装好的类,它的本质是一个可变类型的双向链表。

    //申明
    //using System.Collection.Generic
    LinkedList<int> linkedList = new LinkListed<int>();
    //链表对象需要掌握两个类:LinkedList和LinkedListNode
    
    //增删查改
    //增
    //1.在链表尾部添加元素
    linkedList.AddLast(10);
    
    //2.在链表头部添加元素
    linkedList.AddFirst(20);
    
    //3.在某一个节点之后添加一个节点   要在指定节点 先得得到一个节点
    linkedListNode<int> n = LinkedList.Find(20);
    linkedList.addAfter(n,15);
    
    //4.在某一个节点之前添加一个节点   要在指定节点 先得得到一个节点
    linkedList.addBefore(n,11);
    
    
    
    //删
    //1.移除头节点
    linkedList.RemoveFirst();
    
    //2.移除尾结点
    linkedList.RemoveLast();
    
    //3.移除指定节点 无法通过指定位置直接移除
    linkedList.Remove(20);
    
    //4.清空
    linkedList.Clear();
    
    linkedList.AddLast(1);
    linkedList.AddLast(2);
    linkedList.AddLast(3);
    linkedList.AddLast(4);
    
    //查
    //1.头节点
    linkedListNode<int> first = LinkedList.First; //1
    //2.尾结点
    linkedListNode<int> last = LinkedList.Last; //4
    //3.找到指定值的节点 无法通过下标 只有遍历查找指定位置的元素
    linkedListNode<int> node = LinkedList.Find(3); //去找值为3的节点
    Console.WriteLine(node.Value);
    node = linkedList.Find(5); //找不到会返回空
    //4.判断是否存在
    if(linkedLine.Contains(1))
    {
        Console.WriteLine("链表中存在1");
    }
    
    //改
    //要先得到节点 再改变其中的值
    Console.WriteLine(linkedList.First.Value);
    linkedList.First.Value = 10;
    Console.WriteLine(linkedList.First.Value);
    
    //遍历
    //1.foreach遍历
    foreach (int item in linkedList)
    {
        Console.WriteLine(item);
    }
    //2.通过节点遍历
    //从头到尾
    LinkedListNode<int> nowNode = linkedList.First;
    while(nowNode != null)
    {
        Console.WriteLine(nowNode.Value);
        nowNode = nowNode.Next;
    }
    //从尾到头
    nowNode = linkedList.Last;
    while(nowNode != null)
    {
        Console.WriteLine(nowNode.Value);
        nowNode = nowNode.Previous;
    }
    

    泛型栈和队列

    使用上和之前的Stack和Queue一模一样

    //using System.Collection.Generic
    Stack<int> stack = new Stack<int>();
    Queue<int> queue = new Queue<int>()
    

    委托和事件

    委托

    概念

    委托是函数(方法)的容器,可以理解为表示函数的变量类型。用来存储,传递函数(方法)。

    委托的本质是一个类,用来定义函数(方法)的类型(返回值和参数的类型)。

    不同的 函数(方法)必须对应各自“格式”一直的委托。

    基本语法

    关键字:delegate

    语法:访问修饰符 delegate 返回值 委托名(参数列表)

    写在哪里?

    可以申明再namespace和class语句块中 更多的写在namespace中

    简单记忆委托语法 就是函数申明语法前面加一个delegate关键字

    定义自定义委托:

    访问修饰符默认不写为public 在别的命名空间中也能使用

    private 其他命名空间就不能用了

    一般使用public。

    //申明了一个可以用来储存无参无返回值函数的容器
    //这里只是定义了规则 并没有使用
    delegate void MyFun();
    
    //委托规则的申明是不能重名(同一语句块中)
    //表示用来装载或传递 返回值是int 有一个int参数的函数的 委托 容器规则
    delegate int MyFun2(int a);
    

    使用定义好的委托

    委托变量是函数的容器

    static void Main(string[] args)
    {
        MyFun f = new MyFun(Fun);
        Console.WriteLine("1");
        Console.WriteLine("2");
        Console.WriteLine("3");
        Console.WriteLine("4");
        Console.WriteLine("5");
        f.Invoke(); //123123
        
        MyFun f2 = Fun; //与MyFun f2 = new MyFun(Fun)一样
        Console.WriteLine("1");
        Console.WriteLine("2");
        Console.WriteLine("3");
        Console.WriteLine("4");
        Console.WriteLine("5");
        f2(); //123123
        
        MyFun2 f3 = Fun2;
        Console.WriteLine(f3.(1));
        MyFun2 f4 = new MyFun2(Fun2);
        Console.WriteLine(f4.Invoke(3));
    }
    static void Fun()
    {
        Console.WriteLine("123123");
    }
    static int Fun2(int value)
    {
        return value;
    }
    static void Fun3()
    {
        Console.WriteLine("李四在做什么");
    }
    static string Fun4()
    {
        return "";
    }
    static int Fun5()
    {
        return 1;
    }
    

    委托常用在:

    1.作为类的成员

    2.作为函数的参数

    class Test
    {
        public MyFun fun;
        public MyFun2 fun2;
        
        public MyFun(MyFun fun,MyFun2 fun2)
        {
            //先处理一些别的逻辑 当这些逻辑处理完了 再执行传入的函数
            int i = 1;
            i *= 2;
            i += 2;
            
            this.fun = fun;
            this.fun2 = fun2;
        }
    }
    
    Test t = new Test();
    t.TestFun(Fun,Fun2);
    

    委托变量可以存储多个函数(多播委托)

    MyFun ff = Fun;
    ff += Fun;
    ff();  //会输出两次123123
    

    使用系统自带的委托

    //using System;就可以使用Action 无参无返回值的委托
    Action action = Fun;
    action += Fun;
    action();
    
    //系统给我们自带的返回值为<>里面的类型的委托
    Func<string> funcString = Fun4;
    Func<int> funcInt = Fun5;
    //可以传n个参数的 系统提供了1到16个参数的委托 直接用就行
    Action<int,string> = action2 Fun6;
    //可以传n个参数的 并且有返回值的 系统也提供了16个委托
    Func<int,int> func2 = Fun2;
    
    //自己写一个泛型委托
    delegate T MyFun3<T,K>(T t,K k);
    

    事件

    概念

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

    语法

    访问修饰符 event 委托类型 事件名

    事件的使用:

    • 1.事件是作为成员变量存在于类中
    • 2.委托怎么用 事件就怎么用

    事件相对于委托的区别:

    • 1.不能再类外部赋值
    • 2.不能再类外部调用

    注意:它只能作为成员存在于类和接口以及结构体中

    class Test
    {
        //委托成员变量 用于存储 函数的
        public Action myFun;
        //事件成员变量 用于存储 函数的
        public event Action myEvent;
        
        public Test()
        {
            //事件的使用和委托一模一样 只是有些细微的区别
            myFun = TestFun;
            myFun += TestFun;
            myFun -= TestFun;
            myFun();
            myFun.Invoke();
            myFun = null;
            
            myEvent = TestFun;
            myEvent += TestFun;
            myEvent -= TestFun;
            myEvent();
            myEvent.Invoke();
            myEvent = null;
        }
        
        //如果真的想在外部调用事件 就要在类的内部封装一个方法
        public void DoEvent()
        {
            if(myEvent != null)
            {
                myEvent();
            }
        }
        
        public void TestFun()
        {
            Console.WriteLine("123123");
        }
    }
    
    Test t = new Test();
    //委托可以再外部赋值
    t.myFun = null;
    t.myFun = TestFun2;
    //事件是不能再外部赋值的
    //t.myEvent = null;
    //t.myEvent = TestFun2;
    //虽然不能直接赋值,但是可以+- 去添加移除记录的函数
    t.myEvent += TestFun2;
    t.myEvent -= TestFun2;
    //事件不可以直接赋值
    //t.myEvent = t.myEvent + TestFun2;
    
    //委托是可以再外部调用的
    t.myFun.Invoke();
    t.myFun();
    //事件是不能再外部调用的
    //t.myEvent();
    //t.myEvent.Invoke();
    t.DoEvent();
    
    Action a = TestFun2;
    //事件是不能作为临时变量在函数中使用的
    //event Action ae = TestFun2;
    
    static  void TestFun2()
    {
        
    }
    

    为什么有事件?

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

    匿名函数

    概念

    顾名思义,就是没有名字的函数。

    匿名函数的使用主要是配合委托和事件进行使用。

    脱离委托和事件是不会使用匿名函数的。

    基本语法

    delegate (参数列表)

    {

    ​ 函数逻辑

    }

    何时使用?

    • 函数中传递委托参数时
    • 委托或事件赋值时

    匿名函数的使用

    //1.无参无返回
    //这样申明匿名函数 只是在申明函数而已 还没有调用
    Action a = delegate()
    {
        Console.WriteLine("匿名函数逻辑");
    }; //记住要分号
    //这样才是真正调用匿名函数
    a();
    
    //2.有参
    Action<int,string> b = delegate(int a,string b)
    {
        Console.WriteLine(a);
        Console.WriteLine(b);
    };
    b(100,"123");
    
    //3.有返回值
    Fanc<string> c = delegate()
    {
        return "123";
    };
    c();
    
    //4.一般情况会作为函数参数传递或者作为函数返回值
    Test t = new Test();
    //参数传递
    Action ac = delegate()
    {
        Console.WriteLine("随参数传入的匿名函数逻辑");
    };
    t.Dosomething(100,ac);
    
    t.Dosomething(100, delegate()
                  {
                      Console.WriteLine("随参数传入的匿名函数逻辑");
                  });
    //返回值
    Action ac2 = t.GetFun();
    ac2();
    //一步到位
    t.GetFun()();
    
    class Test
    {
        public Action action;
        //作为参数传递时
        public void DOsomething(int a,Action fun)
        {
            Console.WriteLine(a);
            fun();
        }
        //作为返回值
        public Action GetFun()
        {
            return delegate()
            {
                Console.WriteLine("函数内部返回的一个匿名函数的逻辑");
            };
        }
    }
    

    匿名函数的缺点

    • 添加到委托或事件容器中后 不记录 无法单独移除
    Action ac3 = delegate()
    {
        Console.WriteLine("匿名函数一");
    };
    ac3 += delegate()
    {
        Console.WriteLine("匿名函数二");
    };
    ac3();
    //会输出匿名函数一 匿名函数二
    //因为匿名函数没有名字 所以没有办法指定移除某一个匿名函数
    

    Lambad表达式

    可以将Lambad表达式理解为匿名函数的简写。

    它除了写法不同外,使用上几乎和匿名函数一模一样,都是和委托或者事件配合使用的、

    //lambad表达式
    //(参数列表)=>
    //{  函数体  };
    
    //1.无参无返回值
    Action a = ()=>
    {
        Console.WriteLine("无参无返回值的lambad表达式");
    };
    a();
    //2.有参
    Action<int> b = (int value) =>
    {
        Console.WriteLine("有参数的lambad表达式{0}",value);
    };
    b(100);
    //3.甚至参数类型可以省略 参数类型和委托或事件容器一致
    Action<int> c = (value) =>
    {
        Console.WriteLine("省略参数类型的lambad表达式{0}",value);
    };
    c(200);
    //4.有返回值
    //Func<>最后一个类型是返回类型,前面都是参数
    Func<string,int> d = (value)=>
    {
        Console.WriteLine("有返回值的lambad表达式{0}",value);
        return 1;
    }
    d("123123");
    
    //其它传参使用等和匿名函数一样
    //缺点也和匿名函数一样的
    

    闭包

    内层函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。

    注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

    class Test
    {
        public event Action action;
        public Test()
        {
            int value = 10;
            //这里就形成了闭包 
            //因为当构造函数执行完毕时 其中申明的临时变量value的声明周期被改变了
            action = ()=>
            {
                Console.WriteLine(value)
            };
            
            for(int i=0;i<10;i++)
            {
                action += ()=>
                {
                    Console.WriteLine(i);
                }
            }
            for(int i=0;i<10;i++)
            {
                int index = i;
                action += ()=>
                {
                    Console.WriteLine(index);
                }
            }
        }
        public void DoSomething()
        {
            action();
        }
        
    }
    
    Test t = new Test();
    t.DoSomething();
    //打印出来11个10
    //再y打印1-9
    

    List排序

    List自带的排序方法

    List<int> list = new List<int>();
    List.Add(3);
    List.Add(2);
    List.Add(6);
    List.Add(1);
    List.Add(4);
    List.Add(5);
    for(int i = 0;i < list.Count; i++)
    {
        Console.WriteLine(list[i]);
    }
    //list提供了排序方法 升序排列
    list.Sort();
    for(int i = 0;i < list.Count; i++)
    {
        Console.WriteLine(list[i]);
    }
    //123456
    //ArrayList也自带Sort方法
    
    

    自定义类的排序

    class Item : ICpmparable<Item>
    {
        public int money;
        public Item(int money)
        {
            this.money = momey;
        }
        public int CompareTo(Item other)
        {
            //返回值的含义
            //小于0:放在传入对象的前面
            //等于0:保持当前的位置不变
            //大于0:放在传入对象的后面
            
            //可以简单理解传入对象的位置 就是0
            //如果你的返回为负数,就放在它的左边 也就是前面
            //如果你返回正数 就放在它的右边 也就是后面
            //升序排列
            if(this.money>other.money)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        }
    }
    
    List<Item> itemList = new List<Item>();
    itemList.Add(new Item(45));
    itemList.Add(new Item(10));
    itemList.Add(new Item(99));
    itemList.Add(new Item(24));
    itemList.Add(new Item(100));
    itemList.Add(new Item(12));
    //排序方法
    itemList.Sort();
    for(int i = 0;i < itemList.Count; i++)
    {
        Console.WriteLine(itemList[i].money);
    }
    

    通过委托函数排序

    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(4));
    shopItems.Add(new ShopItem(3));
    shopItems.Add(new ShopItem(5));
    shopItems.Add(new ShopItem(6));
    shopItems.Sort(SortShopItem);
    for(int i = 0;i < shopItems.Count; i++)
    {
        Console.WriteLine(shopItems[i].id);
    }
    
    static int SortShopItem( ShopItem a,ShopItem b)
    {
        //传入的两个对象为列表中的两个对象
        //进行两两的比较 用左边的和右边的条件比较
        //返回值规则和之前的一样 0做标准 负数在左(前) 正数在右
        if(a.ad>b.id)
        {
            return 1;
        }
        else
        {
            return -1;
        }
    }
    

    还可以直接用匿名函数传入

    shopItems.Sort(delegate (ShopItem a,ShopItem b)
                   {
                       if(a.ad>b.id)
                       {
                           return 1;
                       }
                       else
                       {
                           return -1;
                       }
                   });
    //lambad配合三目运算符 完美呈现
    shopItems.Sort((a,b) =>
                   {
                       return a.ad>b.id?1:-1;
                   });
    

    协变逆变

    协变:和谐的变化,自然的变化。因为里氏替换原则,父类可以装子类,所以 子类变父类,比如string变成object。

    感受是和谐的

    逆变:逆常规的变化,不正常的变化。因为里氏替换原则,父类可以装子类,但是子类不能装父类。所以 父类变子类,

    比如object变成string感受是不和谐的。

    协变和逆变是用来修饰泛型的

    协变:out

    逆变:in

    用于泛型中 修饰 泛型字母的

    只有泛型接口和泛型委托能使用的

    //作用
    //1、返回值和参数
    //用out修饰的泛型 只能作为返回值
    delegate T TestOut<out T>(); //括号里面不能写T类型参数了
    //用in修饰的泛型 只能作为参数
    delegate void TestIn<in T>(T t);
    
    interface Tesr<out T>
    {
        T TestFun();
    }
    
    //2.结合里氏替换原则理解
    class Father
    {
        
    }
    class Son:Father
    {
        
    }
    
    //协变 父类总是能被子类替换
    //看起来 就是son ->father
    TestOut<Son> os = ()=>
    {
        return new Son();
    };
    TestOut<Father> of = os;
    Father f= of(); //实际上返回的是 os里面装的函数返回的是Son
    
    //逆变 父类总能被子类替换
    TestIn<Father> iF = (value) =>
    {
        
    };
    TestIn<Son> iS = iF;
    iS(new Son()); //实际调用的是iF
    
    

    多线程

    了解线程前先了解进程

    • 进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。打开一个应用程序就是在操作系统上开启了一个进程。

    进程之间可以相互独立运行,互不干扰。

    进程之间也可以相互访问、操作。

    什么是线程?

    • 操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
    • 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程。

    简单理解线程: 就是代码从上到下运行的一条管道。

    什么是多线程?

    • 我们可以通过代码 开启新的线程。可以同时运行代码的多条“管道”就叫做多线程

    语法相关

    线程类 Thread

    需要引用命名空间 using System.Threading;

    static bool isRuning = true;
    //1.申明一个新的线程
    // 注意 线程执行的代码 需要封装到一个函数中
    // 新线程 将要执行的代码逻辑 被封装到了一个函数语句块中
    Thread t = new Thread(NewThreadLogic); 
    
    //2.启动线程
    t.Start();
    
    //3.设置为后台线程
    //当前台线程都结束了的时候,整个程序也就结束了,即使还有后台线程正在运行
    //后台线程不会防止应用程序的进程被终止掉
    //如果不设置为后台线程 可能导致进程无法正常关闭
    t.IsBackground = true; //设置为后台线程
    
    //4.关闭释放一个线程
    //如果开启的线程中不是死循环 是能够结束的逻辑 那么 不用刻意地去关闭它
    //如果是死循环 想要终止这个线程 有两种方式
    //4.1 死循环中的bool标识
    Console.ReadKey();
    isRuning = false;
    Console.ReadKey();
    
    //4.2 通过线程提供的方法(注意在.Net core 版本中无法终止 会报错)
    try
    {
        t.Abort();
        t = null;
    }
    catch
    {
        
    }
    
    //5.线程休眠
    //让线程休眠多少毫秒 1s=1000ms
    //在哪个线程里执行 就休眠哪个线程
    //Thread.Sleep(1000);
    
    
    static void NewThreadLogic()
    {
        //新开线程 执行代码的逻辑 在该函数语句块中
        while(isRuning)
        {
             Thread.Sleep(1000);
             Console.WriteLine("新开线程代码逻辑");
        }
    }
    

    线程之间共享数据

    • 多个线程使用的内存是共享的,都属于该应用程序(进程)
    • 所以要注意,当多线程同时操作同一片内存区域时可能会出问题
    • 可以通过加锁的形式避免问题。
    static object obj = new object();
    //lock 当我们在多个线程当中想要访问同样的东西 进行逻辑处理时
    //为了避免不必要的逻辑顺序执行的查错
    //lock(引用对象)
    while(true)
    {
        lock(obj)
        {
             Console.SetCursorPosition(0,0);
             Console.ForegroundColor = ConsoleColor.Red;
             Console.Write("⚪");
        }
    
    }
    
    static void NewThreadLogic()
    {
        lock(obj)
        {
            Console.SetCursorPosition(0,0);
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.Write("方形");
        }
    }
    

    多线程对于我们意义

    • 可以用多线程专门处理一些复杂耗时的逻辑
    • 比如寻路、网络通信等等

    预处理器指令

    什么是编译器?

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

    什么是预处理指令?

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

    常见的预处理指令

    1.#define 定义一个符号,类似一个没有值的变量

    undef 取消define定义的符号,让其失效。

    两者一般都写在脚本文件最前面。一般配合if指令使用,或配合特性

    //定义一个符号
    #define Unity4
    #define IOS
    #define Unity2021  
    //取消定义一个符号
    #undef Unity4
    

    2.#if #elif #else #endif 和用if语句一样,一般配合# define定义的符号使用,用于告诉编译器进行编译代码的流程控制。

    如果发现有Unity4这个符号 那么其中包含的代码就会被编译器编译

    可以通过 逻辑或 和 逻辑与 进行多种符号的组合判断

    //如果发现有Unity4的符号 那么其中包含的代码 就会被编译器翻译
    #if Unity4
    Console.WriteLine("版本为Unity4"); //不会打印 因为前面把unity4取消了
    #elif Unity2021 && IOS
    Console.WriteLine("版本为Unity2021");   
    //#warning 这个版本不合法
    //#error z
    
    #else
    Console.WriteLine("其他版本");    
    #endif
    

    3.#warning #error 告诉编译器,是报警告还是报错误 一般还是配合if使用

    反射和特性

    反射

    什么是程序集?

    程序集是经由编译器编译得到的,供进一步编译执行的那个中间产物

    在WINDOWS系统中,它一般表现为后缀为.dll(库文件)或者.exe(可执行文件)的格式

    说人话:

    程序集就是我们写的一个代码集合,我们现在写的所有代码,最终都会被编译器翻译为一个程序集供别人使用

    比如一个代码库文件(dll)或者一个可执行文件(exe)。

    元数据

    元数据就是用来描述数据的数据。

    这个概念不仅仅用于程序上,在别的领域也有元数据

    说人话:

    程序中的类,类中的函数、变量等等信息就是 程序的 元数据

    有关程序以及类型的数据被称为 元数据,它们保存在程序集中

    反射的概念

    程序正在运行时,可以查看其他程序集或者自身的元数据。

    一个运行的程序查看本身或者其它程序的元数据的行为就叫做反射。

    说人话:

    程序在运行时,通过反射可以得到其它程序集或者自己程序集代码的各种信息

    类、函数、变量、对象等等,实例化它们,执行它们,操作它们。

    反射的作用

    因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性。

    • 1.程序运行时得到所有元数据,包括元数据的特性
    • 2.程序运行时实例化对象,操作对象
    • 3.程序运行时创建新对象,用这些对象执行任务
    class Test
    {
        private int i = 1;
        public int j = 1;
        public string str = "123";
        
        public Test()
        {
            
        }
        public Test(int i)
        {
            this.i = i;
        }
        public Test(int i,string str):str(i)
        {
            this.str = str;
        }
        public void Speak()
        {
            Console.WriteLine(i);
        }
    }
    
    

    Type

    获取Type

    //Type(类下信息类)
    //它是反射功能的基础
    //它是访问元数据的主要方式
    //使用Type的成员获取有关类型声明的信息
    获取Type
    //1.万物之父object中的GetType()可以获取对象的Type
    int a = 42;
    Type type = a.GeyType();
    Console.WriteLine(type);
    //输出System.Int32
    
    //2.通过typeof关键字 传入类名 也可以得到对象的Type
    Type type2 = typeof(int);
    Console.WriteLine(type2);
    //输出System.Int32
    
    //3.通过类的名字 也可以获取类型
    // 注意:类名必须包括命名空间
    Type type3 = Type.GeyType("System.Int32");
    Console.WriteLine(type3);
    //输出System.Int32
    //type123它们的值和在堆里的地址都是一样的
    

    得到类的程序集信息

    //可以通过Type得到类型所在程序集信息
    Console.WriteLine(type.Assembly);
    Console.WriteLine(type2.Assembly);
    Console.WriteLine(type3.Assembly);
    //输出三次:System.Private.CoreLib, Version=4.0.0.0, Culture-neutral, PublicKeyToken=7cec85d7ba7798e
    //以上是版本信息
    

    获取类中的公共成员

    //首先得到Type
    Type t = typeof(Test);
    //然后得到所有公共成员
    //需要引用命名空间 using System.Reflection;
    MemberInfo[] infos = t.GetMembers();
    for(int i = 0;i < infos.Length; i++)
    {
        Console.WriteLine(infos[i]);
    }
    //会输出所有公共成员Void Speak()等等
    

    获取类的公共构造函数并调用

    //1.获取所有构造函数
    ConstructorInfo[] ctors = t.GetConstructors();
    for(int i = 0;i < ctors.Length; i++)
    {
        Console.WriteLine(ctors[i]);
    }
    //输出 Void .ctor()
     Void .ctors(Int32) 
     Void .ctors(Int32,System String) 
    
    //2.获取其中一个构造函数传入Type数组 数组中内容按顺序是参数类型
    //得构造函数传入Type数组 数组中内容按顺序是参数类型
    //执行构造函数传入 object数组 表示按顺序传入的参数
    // 2-1得到无参构造
    ConstructorInfo info = t.GetConstructor(new Type[0]);
    //执行无参构造 无参构造 没有参数 传null
    Test obj = info.Invoke(null) as Test;
    Console.WriteLine(obj.j);
    // 2-2得到有参构造
    ConstructorInfo info2 = t.GetConstructor(new Type[]{typeof(int)});
    obj = info2.Invoke(new object[]{ 2 }) as Test;
    Console.WriteLine(obj.str);
    ConstructorInfo info3 = t.GetConstructor(new Type[]{typeof(int),typeof(string)});
    obj = info3.Invoke(new object[]{ 4,"44444" }) as Test;
    Console.WriteLine(obj.str);
    

    获取类的公共成员变量

    //1.得到所有成员变量
    FieldInfo[] fieldInfos = t.GetFields();
    for(int i = 0; i < fieldfos.Length; i++)
    {
        Console.WriteLine(fieldInfos[i]);
    }
    //输出 Int32 j System String str
    
    //2.得到指定名称的公共成员变量
    FieldInof infoJ = t.GetField("j");
    Console.WriteLine(infoJ);
    //输出 Int32 j
    
    //3.通过反射来获取和设置对象的值
    Test test = new Test();
    test.j = 99;
    test.str = "999";
    // 3-1 通过反射 获取对象的某个变量的值
    Console.WriteLine(infoJ.GetValue(test));
    // 3-2 通过反射 设置对象的某个变量的值
    infoJ.SetValue(test,100);
    Console.WriteLine(infoJ.GetValue(test));
    

    获取类的公共成员方法

    //通过Type类中的GetMethod公共 得到类中的方法
    //MethodInfo 是方法的反射信息
    Type strType = typeof(string);
    
    //1.如果存在方法重载 用Type数组表示参数类型
    MethodInfo[] methods = strType.GetMethods();
    for(int i = 0; i < methods.Length; i++)
    {
        Console.WriteLine(methods[i]);
    }
    MethodInfo subStr = strType.GetMethod("Substring",new Type[] {typeof(int),typeof(int)});
    //2.调用该方法
    string str = "Hello,World!";
    //注意:如果是静态方法 Invoke中的第一个参数传null即可
    object result = subStr.Invoke(str,new object[]{7,5});
    Console.WriteLine(result);
    //输出orld!
    

    Actovator

    • 用于快速实例化对象的类
    • 用于将Type对象快捷实例化为对象
    //先得到type
    //然后快速实例化一个对象
    Test testType = typeof(Test);
    //1.无参构造
    Test testObj = Activator.CreateInstance(testType) as Test;
    Console.WriteLine(testObj.str);
    //2.有参数构造
    testObj = Activator.CreateInstance(testType,99) as Test;
    Console.WriteLine(testObj.j); //i是私有的,没办法访问,但是可以通过断点看到i是99
    
    testObj = Activator.CreateInstance(testType,99,"111222") as Test;
    Console.WriteLine(testObj.j); 
    

    Assembly

    程序集类:主要用来加载其它程序集。加载后才能用Type来使用其他程序集中的信息,比如,dll文件(库文件),简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。

    //三种加载程序集的函数
    //一般用来加载同一文件下的其他程序集
    //sembly assembly2 = Assembly.Load("程序集名称");
    
    //一般用来加载不在同一文件下的其他程序集
    //sembly assembly = Assembly.LoadFrom("")
    
    //一般用来加载不在同一文件下的其他程序集
    //Assembly assembly = Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
    //Assembly assembly2 = Assembly.LoadFile("要加载的文件完全限定路径");
    
    //1.先加载一个程序集
    //加一个@能够取消转义字符 找到dll文件复制路径+dll文件名
    Assembly assembly = Assembly.LoadFrom(@"E:C#ArrayListArrayListinDebug
    etcoreapp3.1Lesson_18练习题"); 
    //得到元数据中所有的类型
    Type[] types = assembly.GetTypes();
    for(int i = 0; i < types.length; i++)
    {
        Console.WriteLine(types[i]);
    }
    
    //2.再加载程序集中的一个类对象 之后才能用反射
    Type icon = assembly.GetType("Lession_18练习题.Icon");
    MemberInfo[] members = icon.GetMembers();
    for(int i = 0; i < members.Length; i++)
    {
        Console.WriteLine(members[i]);
    }
    //通过反射 实例化一个icon
    //首先得到枚举Type 来得到可以传入的参数
    Type moveDir = assembly.GetType("Lession_18练习题.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");
    while(true)
    {
        Thread.Sleep(1000); //休眠1s
        clear.Invoke(iocnObj,null);
        move.Invoke(iocnObj,null);
        draw.Invoke(iocnObj,null);
    }
    
    
    //3.类库工程创建
    
    

    为什么要学反射?

    为了之后学习Unity引擎的基本工作原理做铺垫

    Unity引擎的基本工作机制 就是建立在反射的基础上

    特性

    特性是什么?

    • 1.特性是一种允许我们向 程序的程序集 添加 元数据 的语言结构
    • 它是用于保存程序结构信息的某种特殊类型的类
    • 2.特性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。
    • 特性与程序实体关联后,即可在运行是使用反射查询特性信息。
    • 3.特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中。
    • 它可以放置在几乎所有的声明中(类、变量、函数等等申明)

    说人话:

    • 特性的本质是个类
    • 我们可以利用特性类为元数据添加额外信息
    • 比如一个类、成员变量、成员方法等等为他们添加更多额外信息
    • 之后可以通过反射来获取这些额外信息

    自定义特性

    //继承特性基类 Attribute
    class MyCustomAttribute : Attribute
    {
        //特性中的成员 一般根据需求来写
        public string info;
        
        public MyCustomAttribute(string info)
        {
            this.info = info;
        }        
        public void TestFun()
        {
            Console.WriteLine("特性的方法");
        }
    }
    

    特性的使用

    基本语法:[特性名(参数列表)]

    本质上 就是在调用特性类的构造函数

    写在哪?类、函数、变量上一行,表示他们具有该特性信息

    [MyCustom("这是一个我自己写的用于计算的类")]
    class MyClass
    {
        [MyCustom("这是一个成员变量")]
        public int value;
        
        [MyCustom("这是一个用于计算加法的函数")]
        public void TestFun([MyCustom("函数参数")]int a)
        {
            
        }
    }
    
    //特性的使用
    MyClass mc = new MyClass();
    //复习下多种得到type方法
    Type t = mc.GetType();
    //t = typeof(MyClass);
    //t = Type.GetType("Lession21_特性.MyClass");
    
    //判断是否使用了某个特性
    //参数一:特性的类型   
    //参数二:代表是否继承链(属性和事件忽略此参数)
    if(t.IsDefined(typeof(MyCustomAttribute),false))
    {
        Console.WriteKLine("该类型应用了特性");
    }
    
    //获取Type元数据中的所有特性
    object[] array = t.GetCustomAttributes(true);
    for(int i = 0; i < array.Length; i++)
    {
        if(array[i] is MyCustomAttribute)
        {
            Console.WriteLine((array[i] as MyCustomAttribute).info);
            (array[i]  as MyCustomAttribute).TestFun();
        }
    }
    

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

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

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,AllowMultiple = true,Inherited = true)]

    • 参数一:AttributeTargets —— 特性能够用在哪些地方
      参数二:AllowMultiple —— 是否允许多个特性实例用在同一个目标上
      参数三:Inherited —— 特性是否能被派生类和重写成员基础

    系统自带特性——过时特性

    过时特性 Obsolete

    • 用于提示用户 使用的方法等成员以及过时 建议使用新方法

    一般加在函数前的特性

    class TestClass
    {
        //参数一:调用过时方法时 提示的内容
        //参数二:true——使用该方法时会报错 false——使用该方法时直接警告
        [Obsolete("OldSpeak方法已经过时了,请使用Speak方法"),false]
        public void OldSpeak(string str)
        {
            
        }
        
        public void Speak()
        {
            
        }
        //系统自带特性——调用者信息特性
        //系统会通过特性自动传入需要的信息
        public void SpeakCaller(string str,[CallerFilePath]string fileName = "",
                               [CallerLineNumber]int line = 0,[CallerMemberName]string target = "")
    }
    

    系统自带特性——调用者信息特性

    哪个文件调用? CallerFilePath特性

    哪一行调用? CallerLineNumber特性

    哪个函数调用?CallerMemberName特性

    需要引用命名空间 using System.Runtime.CompilerServices;

    一般作为函数参数的特性

    系统自带特性——条件编译特性

    条件编译特性 Conditional

    它会和预处理指令 #define 配合使用

    需要引用命名空间using System.Diagnostics;

    主要可以用在一些调试代码上

    有时想执行有时不想执行的代码

    #define Fun
    
    [Conditional("Fun")]
    static void Fun()
    {
        Console.WriteLine("Fun执行");
    }
    Fun(); //无法调用 只有加上#define Fun才能打印
    

    系统自带特性——外部Dll包函数特性

    DllImport

    用来标记非.Net(C#)的函数,表明该函数在一个外部的DLL中定义。

    一般用来调用C或者C++的DLL包写好的方法

    需要引用命名空间 using System.Runtime.InteropServices

    [DllImport("Test.dll")]
    public static extern int Add(int a,int b);
    

    迭代器

    迭代器是什么?

    • 迭代器有时又称光标,是程序设计的软件设计模式。
    • 迭代器提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的标识。
    • 在表现效果上看:
    • 是可以在容器对象(例如链表或数组)上遍历访问的接口
    • 设计人员无需关心容器对象的内存分配的实现细节
    • 可以用foreach遍历的类,都是实现了迭代器的

    标准迭代器的实现方法

    关键接口:IEnumerable,Enumerator

    命名空间:using System.Collections;

    可以通过同时继承IEnumerable和Enumerator实现其中的方法

    using System;
    using System.Collections;
    
    namespace ConsoleApp1
    {
        class CustomList:IEnumerable,IEnumerator
        {
            private int[] list;
            //从-1开始的光标 用于表示 数据移到了哪个位置
            private int position = -1;
            public CustomList()
            {
                list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
            }
    
            public object Current
            {
                get
                {
                    return list[position];
                }
            }
    
            #region IEnumerable
            public IEnumerator GetEnumerator()
            {
                Reset();
                return this;
            }
            #endregion
    
            public bool MoveNext()
            {
                //移动光标
                ++position;
                //是否溢出 溢出就不合法
                return position < list.Length;
            }
            //areset是重置光标位置 一般写在获取 IEnumeraror对象这个函数中
            //用于第一次重置光标位置
            public void Reset()
            {
                position = -1;
            }
        }
    
    
        class Program
        {
            static void Main(string[] args)
            {
                //foreach本质
                //1.先获取in后面这个对象的 IEnumerator
                //  会调用对象其中的 GetEnumerator 方法 来获取
                //2.执行得到这个IEnumerator对象中的MoveNext方法
                //3.只要MoveNext方法的返回值时true,就会得到Current
                //  然后复制给 item
                CustomList list = new CustomList();
                foreach(int item in list)
                {
                    Console.WriteLine(item);
                }
            }
        }
    }
    

    用yield return 语法糖实现迭代器

    • yield return 是C#提供给我们的语法糖

    • 所谓语法糖,也称糖衣语法

    • 主要作用就是将复杂逻辑简单化,可以增加程序的可读性

    • 从而减少代码出错的机会

    • 关键接口:IEnumerable

    • 命名空间:using System.Collections;

    • 让想要通过foreach遍历的自定义类实现接口中的方法GetEnumerator即可

    class CustomList2 : IEnumerable
        {
            private int[] list;
            public CustomList2()
            {
                list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
            }
            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[] array;
            public CustomList2(params T[] array)
            {
                this.array = array;
            }
            public IEnumerator GetEnumerator()
            {
                for(int i=0;i<array.Length;i++)
                {
                    yield return array[i];
                }
            }
        }
    

    特殊语法

    var隐式类型

    • var是一种特殊的变量类型
    • 它可以用来表示任意类型的变量
    • 注意:
    • 1.var不能作为类的成员 只能用于临时变量申明时
    • 也就是 一般写在函数语句块中
    • 2.var必须初始化
    var i = 5;
    var s = "123";
    var array = new int[] { 1, 2, 3, 4 };
    var list = new List<int>();
    

    设置对象初始值

    • 申明对象时,可以通过直接写大括号的形式初始化公共成员变量和属性
        class Person
        {
            private int money;
            public bool sex;
            public string Name
            {
                get;
                set;
            }
            public int Age
            {
                get;
                set;
            }
            public Person(int money)
            {
                this.money = money;
            }
        }
    
     Person p = new Person(100) { sex = true, Age = 18, Name = "唐老狮" };
     Person p2 = new Person(200) { Age = 18 };
    

    设置集合初始值

    • 申明集合对象时,也可以通过大括号 直接初始化内部属性
    int[] array = new int[]{1,2,3,4,5};
    List<int> listInt = new List<(){1,2,3,4,5,6};
    List<Person> listPerson = new List<Persion>(){
        new Person(200),
        new Person(100){Age = 10},
        new Person(1){sex = true,Name = "唐老狮"},
    };
    Dictionart<int,string> dictionary = new Dictionary<int,string>()
    {
        {1,"123"},{2,"222"}
    };
    

    匿名类型

    • var 变量可以申明为自定义的匿名类型
    var v = new{age = 10,money = 11,name = "小明"};
    Console.WriteLine(v.age);
    Console.WriteLine(v.name);
    

    可空类型

    //1.值类型是不能赋值为空的
    //int c = null;  //会报错,值类型不能赋值为 空的
    //2.申明时 在值类型后面加?可以赋值为空
    int? c = null;
    //3.判断是否为空 
    if(c.HasValue)
    {
        //打印值有这两种方法,一样的
        Console.WriteLine(c);
        Console.WriteLine(c.Value);
    }
    //4.安全获取可空类型值
    int? value = null;
    //  4-1.如果为空 默认返回值类型的默认值
    Console.WriteLine(value.GetValueOrDefault()); //输出0
    //  4-2.也可以指定一个默认值
    Console.WriteLine(value.GetValueOrDefault(100)); //输出100,并没有给value赋值
    Console.WriteLine(value); //无输出
    
    float? f = null;
    double? d = null;
    
    //引用类型
    object o = null;
    if(o! = null)
    {
       Console.WriteLine(o.ToString());
    }
    //相当于是一种语法糖 能够帮助我们自动去判断o是否为空
    //如果是null就不会执行tostring,也不会报错
    Console.WriteLine(o?.ToString());
    
    int[] arrayInt = null;
    Console.WriteLine(array?[]); //不会报错 只是返回空而已
    
    Action action = null;
    if(action != null)
    {
        action();
    }
    action?.Invoke(); //和上面那段相同
    

    空合并操作符

    //空合并操作符??
    //左边值??右边值
    //如果左边值为null 就返回右边值 否则返回左边值
    //只要是可以为null的类型都能用
    //相当于一个三目运算符
    int ? intV = null;
    int intI = intV == null?100 : intV.value;
    intI = intV ?? 100; //和上一句一样
    Console.WriteLine(intI);
    
    string str = null;
    str = str ?? "hahah"; 
    Console.WriteLine(str);
    

    内插字符串

    • 关键符号:$
    • 用$来构造字符串,让字符串中可以拼接变量
    string name = "唐老狮";
    int age = 18;
    Console.WriteLine($"好好学习,{name},年龄:{age}");
    

    单逻辑简略写法

    //当循环或者if语句中只有一句代码时,可以省略大括号
    if(true) Console.WriteLine("123123");//不用大括号
    for(int i = 0;i < 10; i++)
        Console.WriteLine(i);
    
    class  Person
    {
        public bool sex;
        public string Name
        {
            //简略写法
            get => "唐老狮";
            set => set = true;
        }
        public int Add(int x,int y) => x+y;
        public void Speak(string str) => Console.WriteLine(str);
    }
    

    值类型和引用类型

    知识回顾:

    值类型

    • 无符号:byte,ushort,uint,ulong
    • 有符号:sbyte,short,int,long
    • 浮点数:float,double,decimal
    • 特殊:char,bool
    • 枚举:enum
    • 结构体:struct

    引用类型

    • string
    • 数组
    • class
    • interface
    • 委托

    值类型和引用类型的本质区别:

    • 值的具体内容存在栈内存上
    • 引用的具体内容存在堆内存上

    如何判断值类型和引用类型?

    F12进到类型的内部去查看,是class就是引用,是struct就是值。

    语句块

    命名空间 -> 类、接口、结构体 -> 函数、属性、索引器、运算符重载等 -> 条件分支、循环

    • 上层语句块:类、结构体
    • 中层语句块:函数
    • 底层语句块:条件分支、循环等

    我们的逻辑代码写在哪里?

    • 函数、条件分支、循环-中底层语句块中

    我们的变量可以申明在哪里?

    • 上、中、底都能申明变量
    • 上层语句块中:成员变量
    • 中、底层语句块中:临时变量

    变量的生命周期

    • 编程时,大部分都是临时变量

    • 在中底层申明的临时变量(函数、条件分支、循环语句块等)

    • 语句块执行结束

    • 没有被记录的对象将被回收或变成垃圾

    • 值类型:被系统自动回收

    • 引用类型:栈上用于存地址的房间被系统自动回收,堆中具体内容变成垃圾,待下次GC回收

      int i = 1;
      string str = "123";
      
    • 想要不被回收或者不变垃圾

    • 必须将其记录下来

    • 如何记录?

    • 在更高层级记录或者使用静态全局变量记录

    int b = 0;
    while(1)
    {
        b=1;
    }
    

    结构体中的值和引用

    • 结构体本身时值类型

    • 前提:该结构体没有做为其他类的成员

    • 在结构体中的值,栈中存储值具体的内容

    • 在结构体中的引用,堆中存储引用具体的内容

    • 引用类型始终存储在堆中

    • 真正通过结构体使用其中引用类型时知识顺藤摸瓜

    struct TestStruct
    {
        public Test t;
        public int i;
    }
    TestStruct td = new TestStrict();
    

    类中的值和引用

    • 类本身是引用类型

    • 在类中的值,堆中存储具体的值

    • 在类中的引用,堆中存储具体的值

    • 值类型跟大哥走,引用类型一根筋

    数组中的存储规则

    • 数组本身是引用类型
    • 值类型数组,堆中房间存具体内容
    • 引用类型数组,堆中房间存地址

    结构体继承接口

    • 利用里氏替换原则,用接口容器装载结构体存在装箱拆箱
    interface ITest
    {
        int Value
        {
            get;
            set;
        }
    }
    struct TestStruct : ITest
    {
        int value;
        public int Value
        {
            get
            {
                return value;
            }
            set
            {
                this.value = value;
            }
        }
    }
    
    TestStruct obj1 = new TestStruct();
    obj1.Value = 1;
    Console.WriteLine(obj1.Value);  //1
    TestStruct obj2 = obj1;
    obj2.Value = 2;
    Console.WriteLine(obj1.Value);  //1
    Console.WriteLine(obj2.Value);  //2
    
    ITest iObj1 = obj1; //里氏替换原则 父类装子类 装箱 value=1
    ITest iObj2 = iObj1;  //没有在堆里新开房间
    iObj2.Value = 99;
    Console.WriteLine(iObj1.Value);  //99
    Console.WriteLine(iObj2.Value);  //99
    
    TestStruct obj3 = (TesrStruct)iObj1; //拆箱
    
  • 相关阅读:
    Hadoop技术创新方案
    什么是大数据
    大数据框架hadoop服务角色介绍
    学习hadoop需要什么基础
    web开发安全框架中的Apache Shiro的应用
    大数据分布式存储的部署模式:分离式or超融合
    Class类的getSimpleName()
    Eclipse快捷键
    mysql_建立索引的优缺点
    锁表 for update
  • 原文地址:https://www.cnblogs.com/tavee/p/15492246.html
Copyright © 2020-2023  润新知