十六、接口隔离原则、反射、依赖注入
接口隔离原则、反射、特性、依赖注入
接口隔离原则
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
五种原则当中的
i
上一章中的接口,即契约。
契约就是在说两件事,甲方说自己不会多要,乙方会在契约里说自己不会少给。
乙方的不会少给是比较容易做到的,作为服务的提供者,实现一个接口的时候,必须要实现接口里面的所有的方法,如果没有实现所有的方法,那么就会留下抽象方法,自己就变成了一个抽象类,那么仍然不能够实例化,不是一个完整的服务提供者,不是一个具体类。
实现了一个接口的完整服务提供者,接口里面的方法是一定必须实现的。乙方不能少给,这是强制性的,硬性的规定。如果有方法没有实现完全,编译器会检查到报错。
但是甲方不会多要,却是一个软性的规定。所以说这是一个设计方面的问题,需要使用设计原则去约束。 编译器没法去检查,甲方是否多要了。
怎么才能知道,甲方有没有多要呢?就看传给这个调用者的接口类型里,有没有一直没被调用到的方法成员。如果有,那就说明,传进来的接口类型太大了,太胖了。换句话来说,当一个接口太胖了,那么这个接口就是由两个或两个以上的本质不同的小接口合并起来的。所以当把大接口传进来之后,只有一部分被调用到了,而其中一部分就多余出来了。
这种大接口产生的原因的不同,违反接口隔离原则,所带来的不好的后果有两种。
第一种情况
胖接口设计的时候有问题,把太多的功能包含到这个接口里了。把这种含有太多功能的接口传给调用者,这样其中必然有一些功能调用不到。
这样子的话,实现这个接口的类,同时也违反了,单一职责原则。
单一职责原则和接口隔离原则,就是一个硬币的两面,实际上就是一回事。只是接口隔离原则,是从服务的调用者的角度上来看。单一职责原则,是站在服务提供者的角度上来看这个接口。
针对这种情况,我们的解决方法就是将胖接口拆分。拆分小接口,每个小接口都描述单一的功能。将本质不同的功能隔离开来,然后再使用接口封装起来。
接口隔离原则:就是服务的调用者不会多要
using System;namespace IspExample {/* 背景故事:一个女生开车把车撞了* 她男朋友就哄她说,下次给她买个坦克开*/internal class Program {static void Main(string[] args) {/* 我们想要给driver传入的是交通工具* 而不是想要传入能开炮的东西* 所以这里是ITank接口设计的不合理* 应该让ITank继承IVehicle*/Driver driver = new Driver(new Car());driver.Drive();Console.ReadKey();}}/* 此时我们想让Driver类* 也能开Tank,无论怎么改都要改动Driver类* 假如直接将IVehicle接口改为ITanke接口* 那么的确可以使用这个接口,开坦克了,但是这样* 就传进来了一个胖接口,因为那个女生开坦克是当做车开的* Fire方法永远都不会被调用到了* 那么这个设计就违反了接口隔离原则* 解决办法就是,将这个胖接口,分成两个小接口*/class Driver {private IVehicle _vehicle;public Driver(IVehicle vehicle){_vehicle = vehicle;}public void Drive() {_vehicle.Run();}}interface IVehicle {void Run();}class Car : IVehicle {public void Run() {Console.WriteLine("Car is running...");}}class Truck : IVehicle {public void Run() {Console.WriteLine("Truck is running...");}}interface IWeapon{void Fire();}/* 继承多个接口* 让ITank既继承自IVehicle又继承自IWeapon*/interface ITank : IVehicle, IWeapon{}//原本的ITank//interface ITank {// void Fire();// void Run();//}class LightTank : ITank {public void Fire() {Console.WriteLine("Boom!");}public void Run() {Console.WriteLine("Ka ka ka...");}}class MediumTank : ITank {public void Fire() {Console.WriteLine("Boom!!");}public void Run() {Console.WriteLine("Ka! ka! ka!...");}}class HeavyTank : ITank {public void Fire() {Console.WriteLine("Boom!!!");}public void Run() {Console.WriteLine("Ka!! ka!! ka!!...");}}
}
注:在使用接口隔离原则和单一职责原则的时候,不要过犹不及。如果玩得过火了的话,就会产生很多很细碎的里面只有一个方法的接口和类
第二种情况
传入的接口有问题
本应该传一个小接口,结果却传了一个将几个小接口合并起来的大接口。
这可能导致的问题就是,把一些原本合格的服务的提供者当在门外了
就比如上一个例子中,将
Driver
中的代码,改成这种那么就服务的提供者就没有了
Car
和Truck
这两个了class Driver {private ITank _tank;public Driver(ITank tank){_tank = tank;}public void Drive() {_tank.Run();}}
using System;
using System.Collections;namespace IspExample2 {/* 之前有个例子中* 要求计算一组整数的和,* 接口对服务调用者的约束就是,这组整数能够被迭代* 也就是要求,组整数的类型实现了IEnumerable接口*/internal class Program {static void Main(string[] args) {int[] nums1 = { 1, 2, 3, 4, 5};ArrayList nums2 = new ArrayList { 1, 2, 3, 4, 5 };var nums3 = new ReadOnlyCollection(nums1);Console.WriteLine(Sum(nums1));Console.WriteLine(Sum(nums2));Console.WriteLine(Sum(nums3));/* 此时,Sum这个方法是没办法处理nums3的* 因为设置的传入接口太胖了* 我们自己定义的集合只继承了IEnumerable接口* 而没有继承ICollection接口* 我们实际上,只需要有迭代器的数据类型就能够传入* 所以不应该将传入的接口设置的这么胖*/}//static int Sum(ICollection nums)//原本设置的接口//下面是更改后的接口static int Sum(IEnumerable nums) {int sum = 0;foreach (int i in nums) {sum += i;}return sum;}}//有可能我们会设计出一个只实现了IEnumerable接口//而没有实现ICollection接口的类//自己写一个只读的集合class ReadOnlyCollection : IEnumerable {private int[] _array;public ReadOnlyCollection(int[] array){_array = array;}//当外界迭代的时候,需要给一个迭代器public IEnumerator GetEnumerator() {return new Enumerator(this);}public class Enumerator : IEnumerator {private ReadOnlyCollection _collection;private int _head;public Enumerator(ReadOnlyCollection collection){_collection = collection;_head = -1;/* 为什么要初始化为-1是有原因的* 迭代器是先调用判断是否越界的方法的* 而这个时候++_head,然后读取数组的值的时候,就正好是1了*/}//只读属性public object Current {get {/* 这个属性需要拿到传进集合的数组,* 所以是必须传进来一个collection的* 因为要求返回为Object,所以还不能直接返回整数类型,需要装箱*/Object o = _collection._array[_head];//_head++;return o;}}public bool MoveNext() {if (++_head < _collection._array.Length)return true;else return false;}public void Reset() {_head = -1;}}}
}
第三种情况
专门用来展示,显式接口实现
c#语言在接口隔离方面,做得比其他语言都要好,都要彻底。不但能做到接口隔离,甚至还能做到把隔离出来的接口隐藏起来。直到显式的使用这种接口类型的变量,去引用一个实现了这个接口的具体类的实例的时候,这个接口内的方法才能被看见,才能被使用。
杀手不太冷的主角,一面是暖男,一面是冷酷无情的杀手
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace IspExample3 {/* 爱憎分明的杀手* 一面是暖男* 另一面是冷酷无情的*/internal class Program {static void Main(string[] args) {/* 从设计的角度来讲* 杀手在大街上不应该被人随便就能认出来* 即,Kill()方法,不应该能显示出来。* 如果一个接口的方法,我们不想被人轻易地调用的话* 就不应该被人轻易地看到*/IKiller killer = new WarmKiller();killer.Kill();//如果想要调用Love方法有以下几种方式:var wk = (IGentleman)killer;//var wk = (WarmKiller)killer;//var wk = killer as WarmKiller;wk.Love();}}interface IGentleman {void Love();}interface IKiller {void Kill();}class WarmKiller : IGentleman, IKiller {public void Love() {Console.WriteLine("I will love you for ever..."); }/* 这个方法只有* IKiller类型引用的实例才能够调用这个方法*/void IKiller.Kill() {throw new NotImplementedException();}}
}
反射
反射不是C#语言的功能,而是.Net框架的功能。
给一个对象,反射能在不适用
new
操作符,并且也不知道这个对象是什么静态类型的情况下,创建出同类型的对象。还能访问这个对象所带有的各个成员。
这就相当于进一步的解耦。因为在有new
操作符的地方,一定会跟类型,一旦跟了类型,就有了依赖。而且这种依赖还是紧耦合的。现在创建对象可以不适用new
操作符,可以不出现静态类型,那么很多时候这个耦合甚至可弱到忽略不计。
Java开发体系也有这个机制。可以说,这是C#和Java这些托管类型语言与c/c++这些原生类型语言最大的区别之一。
之前学的单元测试,下面要学的依赖注入,还有以后要学的泛型编程,都是基于反射机制的。处于.Net和Java很底层的东西
这么底层的东西,原理一定是很深奥,很复杂。 但是呢,.Net和C#在设计方面十分精妙。一般情况下,我们在使用反射,但是却感觉不到反射的存在。也就是说,一般不会直接接触到反射,大部分情况下,都是使用一些已经封装好了的反射。
很多时候 程序的逻辑,不是我们在写程序的时候就能够确定的。有时候,这个逻辑是到了用户跟程序进行交互的时候,才能确定。这个时候程序已经处在运行状态了。
也就是已经处在动态期了,已经离开开发和编译环境了。如果我们在开发程序的时候,就枚举用户所有能够进行的操作,那么这个程序就会变得非常难维护。
或者我们不可能考虑到用户所能进行的所有情况。所以这个时候,程序需要一种 以不变应万变的能力,这个能力就是反射。
接下来,用两个例子演示反射的神奇功能:
反射原理
.Net平台有两个大的版本:运行在windows上的
.Net FramWork
和可以扩平台的.NET Core
。两个平台都有反射机制,但是类库不太一样。现在使用的是.Net Core
程序,以后使用.Net FramWork
去做反射的时候,可以去查一下对应的API。注意,反射是动态的在内存中进行操作,不要过多的使用反射机制,不然会对程序的性能有所影响。
反射的原理
直接使用反射
using System.Reflection;namespace IspExample4 {/* 背景故事:一个女生开车把车撞了* 她男朋友就哄她说,下次给她买个坦克开*/internal class Program {static void Main(string[] args) {ITank tank = new HeavyTank();//===========分割线===========/* new 后面的HeavyTank就是静态类型* GetType()方法可以获得静态类型的一些信息* 比如这个类型包含哪些方法,哪些属性*/var t = tank.GetType();/* Activator 是激活器* CreateInstance创建t类型的实例,不知道具体的类型* 所以创建的是object类型的*/object o = Activator.CreateInstance(t);//接下来使用反射//获得t类型的名叫Fire的方法MethodInfo fireMi = t.GetMethod("Fire");MethodInfo runMi = t.GetMethod("run");/* public object? Invoke(object? obj, object?[]? parameters);* 第二个参数指的是该方法的是否需要传入一些参数* 即该方法的参数列表*/fireMi.Invoke(o, null);runMi.Invoke(o, null);//这里是直接用的反射,但是大部分情况下不会这么用}}class Driver {private IVehicle _vehicle;public Driver(IVehicle vehicle) {_vehicle = vehicle;}public void Drive() {_vehicle.Run();}}interface IVehicle {void Run();}class Car : IVehicle {public void Run() {Console.WriteLine("Car is running...");}}class Truck : IVehicle {public void Run() {Console.WriteLine("Truck is running...");}}interface IWeapon {void Fire();}/* 继承多个接口* 让ITank既继承自IVehicle又继承自IWeapon*/interface ITank : IVehicle, IWeapon {}class LightTank : ITank {public void Fire() {Console.WriteLine("Boom!");}public void Run() {Console.WriteLine("Ka ka ka...");}}class MediumTank : ITank {public void Fire() {Console.WriteLine("Boom!!");}public void Run() {Console.WriteLine("Ka! ka! ka!...");}}class HeavyTank : ITank {public void Fire() {Console.WriteLine("Boom!!!");}public void Run() {Console.WriteLine("Ka!! ka!! ka!!...");}}
}
依赖注入
(DI – Dependency Injection)缩写是DI。
依赖反转原则的缩写也是DI,Dependency Inversion。
所以此DI非彼DI,但是如果没有依赖反转,也就没有依赖注入。
依赖反转是一个概念,依赖注入是在这个概念的基础之上,结合接口和反射机制,所形成的一种应用。
依赖注入最重要的就是有一个容器Container
,现在使用的容器是 Microsoft.Extensions.DependencyInjection: 这是.NET Core内置的依赖注入容器,提供了基本的DI功能1。也就是ServiceProvider
,把各种各样的类型和这些类型对应的接口放到容器里面。要实例的时候,就向容器要。注册类型的时候,还可以指定以后创建对象的时候,是每次创建一个新对象,还是创建一个单例模式,每次都传同一个实例。这个容器怎么使用,不在这里讲,这里主要是看DependencyInjection
怎么用。
这篇文章有助于理解依赖注入
[C#]理解和入门依赖注入 - 知乎 (zhihu.com)
依赖注入需要借助依赖注入的框架
引入名称空间
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;namespace IspExample4 {internal class Program {static void Main(string[] args) {/* 反射有一种很重要的容器* 叫做container* 这种容器,就不在这里展开了*///服务的提供者var sc = new ServiceCollection();/* typeof()方法用于获取类型的动态信息* AddKeyedScoped()方法有很多重载方法,* 我们现在使用的这个* 第一个参数是基接口* 第二个是谁实现了这个接口* 假如我们没有使用反射,程序中new 了很多的HeavyTank对象* 当有一天我们需要改成MediumTank的时候,就需要改很多个地方* 而使用了反射,就只需要改成* sc.AddKeyedScoped(typeof(ITank), typeof(MediumTank));* 现在这是基础用法*/sc.AddScoped(typeof(ITank), typeof(HeavyTank));var sp = sc.BuildServiceProvider();//============分割线==============ITank tank = sp.GetService<ITank>();tank.Fire();tank.Run();//更强大的功能:sc.AddScoped(typeof(IVehicle), typeof(MediumTank));sc.AddScoped<Driver>();var ss = sc.BuildServiceProvider();//===============分割线============var driver = ss.GetService<Driver>();driver.Drive();/* 当typeof()里面是Car的时候,就是调用的Car的Run方法* 当typeof()里面是MediumTank的时候,就是调用的MediumTank的Run方法*/}}class Driver {private IVehicle _vehicle;public Driver(IVehicle vehicle) {_vehicle = vehicle;}public void Drive() {_vehicle.Run();}}interface IVehicle {void Run();}class Car : IVehicle {public void Run() {Console.WriteLine("Car is running...");}}class Truck : IVehicle {public void Run() {Console.WriteLine("Truck is running...");}}interface IWeapon {void Fire();}/* 继承多个接口* 让ITank既继承自IVehicle又继承自IWeapon*/interface ITank : IVehicle, IWeapon {}class LightTank : ITank {public void Fire() {Console.WriteLine("Boom!");}public void Run() {Console.WriteLine("Ka ka ka...");}}class MediumTank : ITank {public void Fire() {Console.WriteLine("Boom!!");}public void Run() {Console.WriteLine("Ka! ka! ka!...");}}class HeavyTank : ITank {public void Fire() {Console.WriteLine("Boom!!!");}public void Run() {Console.WriteLine("Ka!! ka!! ka!!...");}}
}
反射实现更松的耦合
这种更松的耦合一般使用在插件式编程中。好处是,以主体程序为中心,生成一个生态圈,在这个生态圈中,不断的更新主体程序,于是就有人不断用插件往上面添加新功能并且从中获利。微软的很多东西都是带有这种生态圈的。
插件,不与主体程序一起编译,但是与主体程序一起工作,往往由第三方提供。主体程序和插件的关系,就是不变和万变的关系。
主体程序会发布包含有程序开发接口、API(Application Programming Interface)程序开发包,就是SDK(Software Development Kit)
反射开发会太自由了,发布API可以约束开发者开发第三方插件,同时也减轻开发者的一些劳动。
背景:作为婴儿车的厂商,要发明一种带面板的婴儿车。
这种面板有两排按钮,第一排按钮是一些小动物的头像,第二排按钮是一些数字,按下小动物,再按下数字,就会发出对应次数的叫声。
然后我们设计一个接口,可以让第三方,自己定义动物的种类,自己定义次数,只需要一个USB接口就可以传进去。
纯反射
这是指,不依赖于任何预先定义的接口或基类,而是完全通过反射来检查和操作程序集、类型、成员等。这种方式可以在运行时动态地创建对象、调用方法、访问字段和属性,甚至修改类型的行为。
主体程序:
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
namespace BabyStroller.App {internal class Program {/* 主体程序会将Animals文件夹中* 第三方提供的插件加载进来* 去调用里面所有小动物的Voice(int num)方法* * 在主体程序中主要写:* 利用反射加载插件,拿到这些动物类,然后创建实例* 并且调用实例的Voice方法* 这样的功能*/static void Main(string[] args) {Console.WriteLine(Environment.CurrentDirectory);//查看当前项目所处文件夹,创建Animals文件夹var folder = Path.Combine(Environment.CurrentDirectory, "Animals");var files = Directory.GetFiles(folder);var animalTypes = new List<Type>();foreach ( var file in files) {var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);var types = assembly.GetTypes();//得到加载进来的类型foreach ( var type in types) {//将含有Voice方法的类型加入到List中if (type.GetMethod("Voice") != null){animalTypes.Add(type);}}}/* 开始运行游戏* 很多这种面板里面实际上就是个死循环,* 除非强制关机、或者电池没电,* 不然会一直转下去,或者程序崩溃*/while (true) {//先打印有多少种动物for ( int i = 0; i < animalTypes.Count; i++ ) {Console.WriteLine($"{i + 1}.{animalTypes[i].Name}");}Console.WriteLine("=======================================");//选择动物Console.WriteLine("Please choose animal:");int index = int.Parse( Console.ReadLine() );if( index > animalTypes.Count || index < 1 ) {Console.WriteLine("No such an animal.Try again!");continue;}//选择次数Console.WriteLine("How many times?");int times = int.Parse( Console.ReadLine() );var t = animalTypes[index - 1];var m = t.GetMethod("Voice");//调用无参构造器实例化的对象var o = Activator.CreateInstance(t);//调用该方法,并传入参数列表数组m.Invoke(o, new object[] { times });}}}
}
第三方提供的类
分别有两个类库,一共四个类,为了演示效果,没有做太多的类。
然后将第三方写的类库,生成
再将两个类库生成的.dll
文件添加到主体程序所在文件夹中的Animals
文件夹中
于是就可以成功运行主体程序了。
使用接口的反射
创建一个接口或抽象类来定义操作的契约,然后通过反射动态地加载和实例化实现了这些接口或抽象类的具体类。
使用纯反射,很容易犯错误,比如把方法名字写错了,那么这个类就会被看不到了。
为了避免第三方,开发犯错误,以及减少开发成本。
首先制作接口文件:
Attribute
类用于保护那些未写完的动物类
然后将这个接口项目生成.dll
文件。
接着,在第三方中,添加并使用主体程序发布的接口
然后修改部分代码:
接着,让这两个类库生成.dll
文件
接把新build
的.dll
文件,再次放到主程序所在的Animals
文件夹中,将原来的覆盖掉
然后修改主程序
using BabyStroller.SDK;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
namespace BabyStroller.App {internal class Program {/* 主体程序会将Animals文件夹中* 第三方提供的插件加载进来* 去调用里面所有小动物的Voice(int num)方法* * 在主体程序中主要写:* 利用反射加载插件,拿到这些动物类,然后创建实例* 并且调用实例的Voice方法* 这样的功能*/static void Main(string[] args) {Console.WriteLine(Environment.CurrentDirectory);//查看当前项目所处文件夹,创建Animals文件夹var folder = Path.Combine(Environment.CurrentDirectory, "Animals");var files = Directory.GetFiles(folder);var animalTypes = new List<Type>();foreach ( var file in files) {//遍历文件//得到一个程序集var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);//得到程序集中的类型集合var types = assembly.GetTypes();//得到加载进来的类型foreach ( var t in types) {//遍历这个类型集合//将含有Voice方法的类型加入到List中//if (t.GetMethod("Voice") != null){// animalTypes.Add(t);//}//有了SDK,后就不用这种笨的办法了/* 现在只需要判断一下,* 这个类型是不是IAnimal接口的实现类型* 并且没有被[UnFinishedAttribute]这个特性所修饰*/if(t.GetInterfaces().Contains(typeof(IAnimal))){var isUnfinished = t.GetCustomAttributes(false).Any(a =>a.GetType() == typeof(UnfinishedAttribute));animalTypes.Add(t);}}}/* 开始运行游戏* 很多这种面板里面实际上就是个死循环,* 除非强制关机、或者电池没电,* 不然会一直转下去,或者程序崩溃*/while (true) {//先打印有多少种动物for ( int i = 0; i < animalTypes.Count; i++ ) {Console.WriteLine($"{i + 1}.{animalTypes[i].Name}");} Console.WriteLine("=======================================");//选择动物Console.WriteLine("Please choose animal:");int index = int.Parse( Console.ReadLine() );if( index > animalTypes.Count || index < 1 ) {Console.WriteLine("No such an animal.Try again!");continue;}//选择次数Console.WriteLine("How many times?");int times = int.Parse( Console.ReadLine() );var t = animalTypes[index - 1];var m = t.GetMethod("Voice");//调用无参构造器实例化的对象var o = Activator.CreateInstance(t);//调用该方法,并传入参数列表数组//m.Invoke(o, new object[] { times });var a = o as IAnimal;a.Voice(times);}}}
}
特性
其作用就是在使用反射的时候,通过反射拿到一个方法或一个类看有没有被某个
Attribute
所修饰,然后再做决定,是否调用,是否保留等等。这就是Attribute
的用处。之前单元测试中的带框的英文就是
Attribute
可以看看这篇文章
【Unity】Unity C#基础(十二)特性Attribute、反射Reflection_unity attributeusage-CSDN博客
补充
Activator
是一个用于快速实例化对象的类。比如:用于将Type
信息类对象快速实例化为对象。通过反射来实例化。
Type的一些用法
using System.Reflection;
using System.Runtime.CompilerServices;namespace AllReflectionExample {class Test {private int i = 1;public int j = 0;public string str = "Hello";public Test(){}public Test(int i) {this.i = i;}//对构造器的重载public Test(int i, string str): this(i){this.str = str;}public void Speak() {Console.WriteLine(i);}}internal class Program {static void Main(string[] args) {//Type 是类的信息类#region 获取Type的方式// 1.object中的GetType()方法可以获得Typeint num = 5;Type t = num.GetType();Console.WriteLine(t);// 2.通过typeof关键字 传入类名Type t2 = typeof(int);Console.WriteLine(t2);// 3.通过类的名字 不过必须得是包含名称空间的全称,不然找不到Type t3 = Type.GetType("System.Int32");Console.WriteLine(t3);#endregion#region 获取类中的所有公共成员//先得到信息类Type t4 = typeof(Test);//得到公共成员,需要引用命名空间using System.Reflection;MemberInfo[] infos = t4.GetMembers();for (int i = 0; i < infos.Length; i++) {Console.WriteLine(infos[i]);}#endregion#region 获取类的公共构造函数并调用//1.获取所有构造函数ConstructorInfo[] ctors = t.GetConstructors();for (int i = 0; i < ctors.Length; i++) { Console.WriteLine(ctors[i]);}//2.获取其中一个构造函数 并执行//得构造函数传入 Type数组 数组中内容按顺序是参数类型//执行构造函数传入 object数组 表示按顺序传入的参数//2 - 1得到无参构造ConstructorInfo info = t.GetConstructor(new Type[0]);//执行无参构造函数 没有参数 传nullTest 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, "shaoi"}) as Test;Console.WriteLine(obj.str);#endregion//还可以通过这种方式,获取类的公共其他成员并调用}}
}
Assembly
程序集类,主要用来加载其他程序集,加载后才能用
type
来使用其他程序集中的信息。如果想要使用不是自己程序集中的内容,就需要先加载该程序集,比如
dll
库文件。如果想要详细了解,可以去看其他文章,这里仅做介绍。
在这里,我想说一下,知识是学不完的,但是当我们学到某种程度就足够我们使用了。
如果以后再遇到需要再深入学习,到那个时候,可以再学。
相关文章:

十六、接口隔离原则、反射、依赖注入
接口隔离原则、反射、特性、依赖注入 接口隔离原则 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 五种原则当中的i 上一章中的接口,即契约。 契约就是在说两件事,甲方说自己不会多要,乙方会在…...
Docker 进阶
1、容器数据卷 什么是容器数据卷? 就是当容器内存在了mysql,在里面书写了数据,如果容器删除了,那么数据也就没有了,通过容器数据卷的技术,可以让容器内的数据持久化到Linux服务器上 操作 #docker run -…...

科研学习|论文解读——一种修正评分偏差并精细聚类中心的协同过滤推荐算法
知网链接 一种修正评分偏差并精细聚类中心的协同过滤推荐算法 - 中国知网 (cnki.net) 摘要 协同过滤作为国内外学者普遍关注的推荐算法之一,受评分失真和数据稀疏等问题影响,算法推荐效果不尽如人意。为解决上述问题,本文提出了一种改进的聚类…...

云计算项目十一:构建完整的日志分析平台
检查k8s集群环境,master主机操作,确定是ready 启动harbor [rootharbor ~]# cd /usr/local/harbor [rootharbor harbor]# /usr/local/bin/docker-compose up -d 检查head插件是否启动,如果没有,需要启动 [rootes-0001 ~]# system…...
2.经典项目-海量用户即使通讯系统
1.实现功能-完成注册用户 完成用户注册的步骤(客户端) 1.将User移动到common/message文件夹下 2.在message中新增注册用户的结构体 const (LoginMesType "LoginMes"LoginResMesType "LoginResMes"RegisterMesType "RegisterMes"…...

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的交通标志识别系统详解(深度学习模型+UI界面代码+训练数据集)
摘要:本篇博客详细介绍了利用深度学习构建交通标志识别系统的过程,并提供了完整的实现代码。该系统采用了先进的YOLOv8算法,并与YOLOv7、YOLOv6、YOLOv5等早期版本进行了性能评估对比,分析了性能指标如mAP、F1 Score等。文章深入探…...

VMware下创建虚拟机
Centos7是比较常用的一个Linux发行版本,在国内的使用比例比较高 安装完VMware一定要检查虚拟网卡有没有安装成功,如果没有VMnet1和VMnet8 虚拟机是无法上网的,就需要卸载重启电脑重新安装 控制面板—网络和Internet—网络连接 快捷方式打开&a…...

基于Ambari搭建大数据分析平台
一、部署工具简介 1. Hadoop生态系统 Hadoop big data ecosystem in Apache stack 2. Hadoop的发行版本 Hadoop的发行版除了Apache的开源版本之外,国外比较流行的还有:Cloudera发行版(CDH)、Hortonworks发行版(HDP)、MapR等&am…...
Vue template到render过程,以及render的调用时机
Vue template到render过程 vue的模版编译过程主要如下:template -> ast -> render函数(1)调用parse方法将template转化为ast(抽象语法树)(2)对静态节点做优化(3)生…...

阿里云服务器Ngnix配置SSL证书开启HTTPS访问
文章目录 前言一、SSL证书是什么?二、如何获取免费SSL证书三、Ngnix配置SSL证书总结 前言 很多童鞋的网站默认访问都是通过80端口的Http服务进行访问,往往都会提示不安全,很多人以为Https有多么高大上,实际不然,他只是…...

12 list的使用
文档介绍 文档介绍 1.list是可以在常数范围内的任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代 2.list的底层是带头双向链表循环结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和…...
控件交互与视图交互的区别
在实际应用中,控件交互和视图交互的区别主要体现在以下几个方面: (1)关注的对象不同:控件交互更关注于界面中的单个控件如何响应用户的操作,例如按钮的点击、列表项的滑动等。而视图交互则更关注于整个界面的布局、导航和交互设计…...
打包 加載AB包 webGl TextMeshPro 變紫色的原因
1.打包 加載AB包 webGl TextMeshPro 變紫色的原因 編輯器命令行https://docs.unity3d.com/cn/2019.4/Manual/CommandLineArguments.html 1.UnityHub 切換命令行參數 -force-gles 2.-force-gles(仅限 Windows)| 使 Editor 使用 OpenGL for Embedded Sys…...
美易官方:去年全球企业派息1.66万亿美元创新高
去年全球企业派息总额达到了1.66万亿美元,创下了历史新高。这一数字不仅彰显了全球企业的盈利能力和财务稳健性,也反映了它们对股东的责任感和对未来发展的信心。在这一背景下,微软和苹果这两家科技巨头在派息方面的表现尤为引人注目。 微软是…...

基于Springboot的面向智慧教育的实习实践系统设计与实现(有报告)。Javaee项目,springboot项目。
演示视频: 基于Springboot的面向智慧教育的实习实践系统设计与实现(有报告)。Javaee项目,springboot项目。 项目介绍: 采用M(model)V(view)C(controller&…...

【数据库-黑马笔记】基础-SQL
本文参考b站黑马数据库视频,总结详细全面的笔记 ,可结合视频观看1~26集 MYSQL 的基础知识框架如下 目录 一、MYSQL概述 1、数据库相关概念 2、MYSQL的安装及启动 二、SQL 1、DDL【Data Defination】 2、DML【Data Manipulation】 ①、插入 ②、更新和删除 3、 DQL【Data…...
MySQL性能分析:性能模式和慢查询日志的使用
目录 一、性能模式 步骤1. 启用性能模式 步骤2. 查询性能数据 步骤3. 分析性能数据 步骤4. 优化与调整 注意事项 二、慢查询日志 步骤1. 启用慢查询日志...
【哈希表算法题记录】15. 三数之和,18. 四数之和——双指针法
题目链接 15. 三数之和 思路 这题虽然放在哈希表的分类里面,但是用双指针法会更高效。 之前的双指针我们要么是一头left一尾right,要么是快fast慢slow指针。这里是要计算三个数的和,我们首先对数组进行从小到大的排序,先固定一…...

代码随想录算法训练营Day44 ||leetCode 完全背包 || 518. 零钱兑换 II || 377. 组合总和 Ⅳ
完全背包 518. 零钱兑换 II 遍历硬币和金额,累加所有可能 class Solution { public:int change(int amount, vector<int>& coins) {vector<int> dp(amount1,0);dp[0]1;for (int i 0; i < coins.size();i){for(int j coins[i]; j < amount;…...

RabbitMQ发布确认高级版
1.前言 在生产环境中由于一些不明原因,导致 RabbitMQ 重启,在 RabbitMQ 重启期间生产者消息投递失败, 导致消息丢失,需要手动处理和恢复。于是,我们开始思考,如何才能进行 RabbitMQ 的消息可靠投递呢&…...

腾讯开源视频生成工具 HunyuanVideo-Avatar,上传一张图+一段音频,就能让图中的人物、动物甚至虚拟角色“活”过来,开口说话、唱歌、演相声!
腾讯混元团队提出的 HunyuanVideo-Avatar 是一个基于多模态扩散变换器(MM-DiT)的模型,能够生成动态、情绪可控和多角色对话视频。支持仅 10GB VRAM 的单 GPU运行,支持多种下游任务和应用。例如生成会说话的虚拟形象视频࿰…...

设计模式基础概念(行为模式):模板方法模式 (Template Method)
概述 模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 是基于继承的代码复用的基本技术,模板方法模式的类结构图中,只有继承关系。 需要开发抽象类和具体子…...
ASR技术(自动语音识别)深度解析
ASR技术(自动语音识别)深度解析 自动语音识别(Automatic Speech Recognition,ASR)是将人类语音转换为文本的核心技术,以下是其全面解析: 一、技术原理架构 #mermaid-svg-QlJOWpMtlGi9LNeF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:1…...

智慧零售管理中的客流统计与属性分析
智慧零售管理中的视觉分析技术应用 一、背景与需求 随着智慧零售的快速发展,传统零售门店面临管理效率低、安全风险高、客户体验差等问题。通过视觉分析技术,智慧零售管理系统可实现对门店内人员行为的实时监控与数据分析,从而提升运营效率…...
Python 函数全攻略:函数进阶(生成器、闭包、内置函数、装饰器、推导式)
一、默认参数中的易错点 问题: 当函数的默认参数是可变类型(如 list, dict)时,存在“坑”。 现象: def func(a2=[]): # a2 默认是一个空列表a2.append(2)print(a2)func() # 第一次调用,a2 默认为 [],输出 [2] func([]) # 传入新列表,输出 [2] func([1]) # 传入带元素的…...

重构城市应急指挥布控策略 ——无人机智能视频监控的破局之道
在突发事件、高空巡查、边远区域布控中,传统摄像头常常“看不到、跟不上、调不动”。无人机智能视频监控系统,打破地面视角局限,以“高空布控 AI分析 实时响应”赋能政企单位智能化管理。在城市应急指挥中心的大屏上,一场暴雨正…...

Microsoft前后端不分离编程新风向:cshtml
文章目录 什么是CSHTML?基础语法内联表达式代码块控制结构 布局页面_ViewStart.cshtml_Layout.cshtml使用布局 模型绑定强类型视图模型集合 HTML辅助方法基本表单验证 局部视图创建局部视图使用局部视图 高级特性视图组件依赖注入Tag Helpers 性能优化缓存捆绑和压缩…...
Java泛型中的通配符详解
无界通配符 通配符的必要性 通过WrapperUtil类的示例可以清晰展示通配符的使用场景。假设我们需要为Wrapper类创建一个工具类WrapperUtil,其中包含一个静态方法printDetails(),该方法需要处理任意类型的Wrapper对象。最初的实现尝试如下: …...
消息队列处理模式:流式与批处理的艺术
🌊 消息队列处理模式:流式与批处理的艺术 📌 深入解析现代分布式系统中的数据处理范式 一、流式处理:实时数据的"活水" 在大数据时代,流式处理已成为实时分析的核心技术。它将数据视为无限的流,…...

Cursor快速梳理ipynb文件Prompt
1. 整体鸟瞰 请在不运行代码的前提下,总结 <文件名.ipynb> 的主要目的、核心逻辑流程和输出结果。阅读整个项目目录,列出每个 .ipynb / .py 文件的角色,以及它们之间的数据依赖关系(输入→处理→输出)。2. 结构…...