学习了:协变out,逆变in,预处理器指令,多线程,反射
1.协变逆变
协变:
和谐的变化.自然的变化
因为里氏替换原则 父类可以装子类
所以 子类变父类
如 string 变成 object感受上是和谐的
逆变:
逆常规的变化。不正常的变化
因为里氏替换原则 子类不能装父类
所以 父类变子类
比如 object 变成 string
感受是不正常的
协变和逆变是来修饰泛型的
协变:out
逆变:in
用于在泛型中 修饰 泛型字母的
只有在泛型接口和泛型委托能使用
在泛型类和结构体中不能使用
作用:
1.返回值 和 参数
用out修饰的泛型 只能作为返回值
delegate T Testout<out T>();
用in修饰的泛型,只能作为参数
delegate void Testin<in T>(T t);
2.结合里氏替换原则理解
class Son : Father { }
class Father { }
协变:父类泛型委托装子类泛型委托
Testout<Son> os= () => { return new Son(); };
//协变:父类装子类,子类变父类,符合里氏替换原则
Testout<Father> of = os;//父类泛型委托装子类泛型委托,协变,out
Father father = of();//实际上of()返回son类型,父类装子类
逆变:子类泛型委托装父类泛型委托
Testin<Father> iF = (value)=> { };
Testin<Son> iS = iF;//子类泛型委托装父类泛型委托,逆变,in
iS(new Son());//实际上是调用IF,IF里的父类Father类型装载了传进去的子类Son,也是父类装了子类,也是符合里氏替换原则
总结
协变 out 父类泛型委托装子类泛型委托
逆变 in 子类泛型委托装父类泛型委托
用来修饰 泛型替代符 且只能修饰接口和委托中的泛型
out修饰的泛型类型只能作为返回值
in修饰的泛型类型只能作为参数
用out in修饰的泛型委托,可以相互装载(存在父子关系的条件下)
请讲述协变逆变有什么作用
1.用来修饰泛型替代符,只能用于泛型委托和泛型接口中
in 逆变修饰的泛型类型只能作为参数
out 协变修饰的泛型类型只能作为返回值
2.遵循里氏替换原则,用out和in修饰的泛型委托,如果类型是父子关系,那么可以互相装载
协变: 父类泛型委托装子类泛型委托
逆变: 子类泛型委托装父类泛型委托
//通过代码体现协变逆变
delegate T testOut<out T>();
delegate void testIn<in T>(T t);
//testOut<Son> ts= () => { return new Son(); };
//testOut<Father> tf = ts;协变
// Father f = tf();//符合里氏替换原则
//testIn<Father> sf = (value) => { };
//testIn<Son> ss = sf;逆变
// ss(new Son());//符合里氏替换原则
2.预处理器指令
什么是编译器?
编译器是一种翻译程序,将源语言程序翻译为目标语言程序
源语言程序:c#、c、c++、java等
目标语言程序:二进制数表示的伪机器代码程序
什么是预处理器指令?
指导编译器 在实际编译开始之前对信息进行预处理
预处理器指令 都是以#开始 例如#region #endregion
预处理器指令不是语句,所以他们不以分号;结束
常见的预处理器指令
1
#define
定义一个符号,类似一个没有值的变量
#undef
取消define定义的符号,让其失效
两者都是写在脚本文件最前面
一般配合 if指令使用 或配合特性
2
#if
#elif
#else
#endif
和if语句一样,一般配合#define定义的符号使用
用于告诉编译器进行编译代码的流程控制
#if Unity2017 && IOS
Console.WriteLine("Unity2017 IOS");
#elif Unity5
Console.WriteLine("Unity5");
#endif
如果发现有Unity2017且存在IOS这两个符号,那么里面包含的代码就会被翻译,同样支持通过||或者&&进行多种条件判断
3
#warning
#error
告诉编译器
是报警还是报错
一般还是配合#if使用
#if Unity2017 && IOS
Console.WriteLine("Unity2017 IOS");
#elif Unity5
Console.WriteLine("Unity5");
#else
#warning 这个版本不合法
#error 版本出错
#endif
总结
预处理器指令
可以让代码还没有编译之前就可以进行一些预处理判断
在Unity常用于进行一些平台或版本的判断
决定不同版本或平台执行不同的代码逻辑
3.多线程
了解线程先了解进程
打开一个应用程序,就相当一个进程
进程之间可以相互独立运行,互不干扰
什么是线程
操作系统能过进行运算调度的最小单位
它能包含在进程之中 是进程中的实际运作单位
一条线程指的是进程中一个单一顺序的控制流 一个进程中可以并发多个线程
我们目前写的程序 都在主线程中
简单理解线程:就是代码从上到下运行的一条”管道“
什么是多线程
我们可以通过代码 开启新的线程
可以同时运行代码的多条”管道“,就是多线程
语法相关
线程类 Thread
需要引用命名空间System.Threading
程序的入口Main函数相当于一个主线程
3.1.新进程如何声明?
注意 线程执行代码 需要封装到一个函数中
新线程 将执行的代码逻辑 封装到了一个函数语句块中
Thread thread = new Thread(NewThreadLoad);
这里NewThreadLoad就是新进程的函数语句块,要求NewThreadLoad是无参无返回值的。
3.2.进程的启动
thread.Start();
3.3后台线程
新建的线程默认为前台线程,其若为死循环,可能导致进程无法随主线程执行而正常关闭
此时我们就需要后台进程了
后台线程:当前台线程都结束了的时候,整个程序也就结束了,即使还有后台线程正在运行
转变为后台线程也很简单:
thread.IsBackground = true;
3.4关闭释放线程
如果开启的线程不是死循环,是能够结束的逻辑,不用刻意去关闭它
如果是死循环 想要种植这个线程 有两种方式
一种是死循环用bool标识
Console.ReadKey();
isRunning = false;
static void NewThreadLoad()
{
//新开线程 执行的代码逻辑 在该函数中
while (isRunning)
{
//Thread.Sleep(1000);
//Console.WriteLine("新开线程的代码逻辑");
lock (obj)
{
Console.SetCursorPosition(10, 6);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("1");
}
}
}
另一种是通过线程提供的方法(注意在 .Net core版本中无法中止 会报错)
中止线程
try
{
thread.Abort();
thread = null;
}catch (Exception ex)
{
}
3.5进程休眠
线程休眠 Thread.Sleep()
让线程休眠多少毫秒 1s=1000ms
在哪个线程里执行就休眠哪个
Thread.Sleep(1000);//休眠1s
3.6 lock加锁
线程之间共享数据
多个线程使用的内存是共享的,都属于该应用程序(进程)
所以要注意,当多线程 同时操作同一片内存区域时可能会出现问题
可以通过加锁的形式避免问题
当我们在多个线程当中想要访问同样的东西,进行逻辑处理时,为了避免不必要的逻辑顺序执行的差错,我们就要进行加锁lock操作
语法:
lock(引用类型对象)//发现其锁住就等待再执行,未锁就执行代码块代码并将其锁住,执行完代码块内容就解锁
{
//加锁代码块
}
lock对线程的效率会有一定影响,但能避免不必要的逻辑顺序执行的差错
3.7多线程的意义
多线程对于我们的意义
可以用多线程专门处理一些复杂耗时的逻辑
比如 寻路、网络通信等等
总结
多线程是多个可以同时执行代码逻辑的"管道"
可以通过代码开启多线程
可以通过多线程处理一些影响主线程流畅度的逻辑
关键字 Thread
4.反射
什么是程序集
程序集是经由编译器编译得到的,供进一步编译执行的那个中间产物
一般表现为.dll(库文件)或者.exe(可执行文件)的格式
简单来说就是我们写的一个代码集合,我们写的代码最终会被编译器翻译为一个程序集供别人使用
什么是元数据
程序中的类、类中的函数、变量等等信息就是 程序 的 元数据
有关程序以及类型的数据被称为 元数据 ,它们保存在程序集中
反射的概念
在程序运行时,通过反射可以得到其他程序集或者自己程序集代码的各种信息
类、函数、变量、对象等等,实例化它们、执行它们、操作它们
反射的作用
因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性
1. 程序运行时得到所有元数据,包括元数据的特性
2. 程序运行时,实例化对象,操作对象
3. 程序运行时创建新对象,用这些对象执行人物
反射语法
4.1.Type (类的信息类) 反射功能的基础 使用Type的成员获取有关类型声明的信息
有关类型的成员(如构造函数、方法、字段、属性和类的事件)
获取Type的三种方法
4.1.1.万物之父object中的GetType()可以获取对象的Type
int a = 42;
Type type = a.GetType();
Console.WriteLine(type);
4.1.2通过typeof关键字,传入类名,也可以得到对象的type
Type type2=typeof(int);
Console.WriteLine(type2);
4.1.3.通过类名来获取类型 类名必须要包含命名空间 不然找不到
Type type3 = Type.GetType("System.Int32");
Console.WriteLine(type3);
//这里的type1、type2、type3指向同一块堆内存空间
//可以通过Type得到类型所在的程序集信息
Console.WriteLine(type.Assembly);
4.2获得类中所有公共成员信息
首先得到Type
type = typeof(Test);
//需要引用命名空间 using System.Reflection;
//MemberInfo 是公共成员的反射信息
MemberInfo[] members = type.GetMembers();//得到所有公共成员
for (int i = 0; i < members.Length; i++)
{
Console.WriteLine(members[i]);
}
4.3获得类中公开构造函数信息
4.3.1获取所有构造函数并调用实例化对象(后面可以使用 Activator.CreateInstance()更为便利地实例化)
GetConstructor得到构造函数
ConstructorInfo是构造函数的反射信息
ConstructorInfo[] constructors = type.GetConstructors();
for(int i=0;i<constructors.Length; i++)
{
Console.WriteLine(constructors[i]);
}
4.3.2具体指定构造函数调用:
得到构造函数传入 Type数组 数组中内容按顺序时参数类型
执行构造函数传入 object数组 数组中的内容按顺序表示传入的参数
得到无参构造信息
ConstructorInfo info = type.GetConstructor(new Type[0]);
执行无参构造.Invoke()调用 返回一个object对象
Test test = info.Invoke(null) as Test;//无参构造没有参数,传null即可
得到有参构造信息
ConstructorInfo info2 = type.GetConstructor (new Type[] { typeof(int)});//int类型构造
Test obj = info2.Invoke(new object[] { 2}) as Test;
ConstructorInfo info3 = type.GetConstructor (new Type[] {typeof(int),typeof(string)});//int、string类型构造
obj = info3.Invoke(new object[] { 4, "4444444" }) as Test;
4.4获得类中公开成员变量信息
通过GetField来得到成员变量信息
FieldInfo是成员变量的反射信息
4.4.1得到所有成员变量信息
FieldInfo[] fields = type.GetFields();
for(int i=0;i<fields.Length;i++)
{
Console.WriteLine(fields[i]);
}
4.4.2得到指定名称的公共成员变量信息 根据变量名得到
FieldInfo infoJ = type.GetField("j");
Console.WriteLine(infoJ);
4.4.3通过反射来获取和设置对象的值
获取
Test t = new Test();
t.j = 2;
t.str = "test";
Console.WriteLine(infoJ.GetValue(t));//.GetValue(t),得到t对象的j变量的值
设置
infoJ.SetValue(t, 100);//.SetVale(t,100),设置t对象的j变量值为100
4.5获得类当中的公共成员方法信息
通过Type类中的GetMethod方法 得到对应方法信息
MethodInfo 是方法的反射信息
4.5.1获得所有公共成员方法信息 .GetMethods()
Type strType =typeof(string);
MethodInfo[] methodInfos = strType.GetMethods();
for(int i = 0; i < methodInfos.Length; i++)
{
Console.WriteLine(methodInfos[i]);
}
4.5.2获得指定公开成员方法信息 .GetMethod()
MethodInfo subStr = strType.GetMethod("Substring", new Type[] { typeof(int), typeof(int) });//第一个参数是函数名,第二个参数传Type数组来指定得到该函数的重载类型
string st = "HelloWorld";
st= subStr.Invoke(st, new Object[] { 5, 3 }) as string;
//Invoke里第一个参数表示哪个对象调用这个方法,如果是静态方法,第一个参数传null即可
//第二个参数为Object数组表示参入的参数列表,如果为无参可填null
Console.WriteLine(st);
4.6Assembly
程序集类
主要用来加载其他程序集,加载后
才能用Type来使用其他程序集中的信息
如果想要使用不是自己程序集中的内容。需要先加载程序集
比如 dll文件
简单的把库文件看成一种代码厂库,它提供给使用者一些可以直接拿来用的变量、函数或类
三种加载程序集的函数
一般用来加载在同一文件下的其他程序集
Assembly assembly2 = Assembly.Load("程序集名称");
一般用来加载同在同一文件下的其他程序集
Assembly assembly = Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
Assembly assembly3 = Assembly.LoadFile("要加载的文件的完全限定路径");
4.7Activator
用于快速实例化对象的类
用于将Type对象快捷实例化为对象
先得到一个type
Type testType = typeof(Test);
同样返回一个Object类型
Test testObj = Activator.CreateInstance(testType) as Test;//无参
Console.WriteLine(testObj.str);
testObj = Activator.CreateInstance(testType, 10, "WEINI") as Test;//第一个参数为实例化对象的Type,后面的参数与构造函数所对应
Console.WriteLine(testObj.str);
反射实例:
1.先加载一个指定程序集
Assembly assembly = Assembly.LoadFrom("C:\\Users\\86135\\Desktop\\作业\\委托\\多线程练习题\\bin\\Debug\\net7.0\\多线程练习题");
Type[] types =assembly.GetTypes();
for(int i = 0; i < types.Length; i++)
{
Console.WriteLine("多线程练习题:"+types[i]);
}
//2.再加载程序中的一个类对象 之后才能使用反射
Type icon = assembly.GetType("Icon");
MemberInfo[] memberInfos = icon.GetMembers();
for(int i=0; i < memberInfos.Length; i++)
{
Console.WriteLine(memberInfos[i]);
}
//实例化
Type moveDir = assembly.GetType("Dir");
FieldInfo down = moveDir.GetField("down");//枚举比较特殊,可以通过right.GetValue(null)直接用
Object iconobj = Activator.CreateInstance(icon,down.GetValue(null));
//通过反射得到方法
MethodInfo Init = icon.GetMethod("Init");
MethodInfo Clear = icon.GetMethod("Clear");
MethodInfo Draw = icon.GetMethod("Draw");
MethodInfo checkDir = icon.GetMethod("checkDir");
MethodInfo Move = icon.GetMethod("Move");
Init.Invoke(iconobj, null);
while (true)
{
Clear.Invoke(iconobj, null);
Move.Invoke(iconobj , null);
Draw.Invoke(iconobj, null);
}
总结
反射
在程序运行时,通过反射可以得到其他程序集或者自己程序集代码的各种信息
类、函数、变量、对象等等,实例化它们,执行它们,操作它们
关键类
Type
Assembly
Activator