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; //拆箱
原文地址:https://www.cnblogs.com/tavee/p/15492246.html