反射
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。
您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
优缺点
优点:
- 1、反射提高了程序的灵活性和扩展性。
- 2、降低耦合性,提高自适应能力。
- 3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
- 1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
- 2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
反射(Reflection)的用途
反射(Reflection)有下列用途:
- 它允许在运行时查看特性(attribute)信息。
- 它允许审查集合中的各种类型,以及实例化这些类型。
- 它允许延迟绑定的方法和属性(property)。
- 它允许在运行时创建新类型,然后使用这些类型执行一些任务。
依赖反转和依赖注入
依赖反转原则(Dependency inversion principle,DIP)是指一种特定的解耦形式,使得高层次的类不依赖于低层次的类的实现细节,依赖关系被颠倒(反转),从而使得低层次类依赖于高层次类的需求抽象。
该原则规定:
- 高层次的类不应该依赖于低层次的类,两者都应该依赖于抽象接口。
- 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。
在传统的应用架构中,低层次的组件设计用于被高层次的组件使用,这一点提供了逐步的构建一个复杂系统的可能。在这种结构下,高层次的组件直接依赖于低层次的组件去实现一些任务。这种对于低层次组件的依赖限制了高层次组件被重用的可行性。
依赖反转原则的目的是把高层次组件从对低层次组件的依赖中解耦出来,这样使得重用不同层级的组件实现变得可能。
按照常理,软件划分层也好,模块也厚,通常都是将相同语义的元素放在一起,因此接口与其实现(类)应该处于一层或模块之中。如下图所示
这看似好像没有问题,但是软件的高层应用可能会发生变化,即来自客户的需求会发生变化(这是常事儿啊)。当高层的应用发生了改变,那它依赖的低层对象所提供的服务也很可能要发生变化,因为高层要完成新的业务,低层要负责提供对应的服务。那么问题来了,谁来约定这个接口提供什么样的服务?按照前面的逻辑,接口和实现放在低层中,那应该由低层提供,可是低层开发人员并不负责高层的应用逻辑。低层应该应该只关心自己那点事儿,即负责响应高层的需求,去按照需求提供实现服务。但现在接口放在低层维护,就应该由低层的开发人员负责体现需求的接口的“变更”(提供新的服务)。这样会发现这样的情况,即负责高层实现的开发人员,他们拥有需求,但无法定义描述需求的接口;负责低层的开发人员,他们不管需求,只应提供具体实现,却要维护和应用需求有关的接口。这不就出现了很大的矛盾吗?
因此,为了解决这样的矛盾,人们提出将本应放在低层的接口放在高层,低层的实现依赖高层提供的接口,去实现相应的服务(请参见上图所示)。本人认为这才是DIP中“倒置”的真正含义所在。
关于依赖注入,可以看下方链接
依赖注入的简单理解 - nowthink - 博客园 (cnblogs.com)
.Net的依赖注入框架是using Microsoft.Extensions.DependencyInjection;
反射的第一个用处:依赖注入的事例代码
using System;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
namespace IspExample
{
class Program
{
static void Main(string[] args)
{
//生成容器sp
var sc = new ServiceCollection();
sc.AddScoped(typeof(ITank),typeof(HeavyTank));//参数1:typeof(接口名),参数2:typeof(接口实现名),注册服务,当实例化时就找到匹配这些接口和实现的实例注入到构造器中。
sc.AddScoped(typeof(IVehicle), typeof(Car));
sc.AddScoped<Driver>();//构造器Driver
var sp = sc.BuildServiceProvider();//建立容器
//---------分割线-----------
//分割线以上是一次性的注册,在程序启动后注册,
//分割线以下,在程序的其他地方,只要你能看到sp的地方都可以用,不再有new操作符,从container里去调对象
ITank tank = sp.GetService<ITank>();
tank.Fire();///Boom!!!
tank.Run();///Ka!! ka!! ka!!
//当上面addscoped中后面的typeof中换成MediumTank,上面输出的就是中型tank的方法。
var driver = sp.GetService<Driver>();//找到符合IVehicle接口的实例car赋给driver,等同于var driver = new Driver(new Car());
driver.Drive();//car is running...
}
}
class Driver
{
private IVehicle _vehicle;
public Driver(IVehicle vehicle)
{
_vehicle = vehicle;
}
public void Drive()
{
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
interface IWeapon
{
void Fire();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("Truck is running...");
}
}
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!!");
}
}
}
反射的第二个用处:更松的耦合,插件式开发编程
插件和主题程序不一起编译,但一起工作
例子大致内容是,你是婴儿车厂商,将婴儿车的面板上按照数字顺序显示一排小动物头像,然后可以选择某个小动物,再点击数字,就可以放出那个动物的叫声多少次。
让第三方程序,一直扩展小动物的叫声,程序就可以一直进步,调用第三方插件就可以了。
主程序:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Loader;
namespace ReflectionExample
{
class Program
{
static void Main(string[] args)
{
//找到Animals文件夹
var folder =Path.Combine(Environment.CurrentDirectory,"Animals");
var files = Directory.GetFiles(folder);
var animalsTypes = new List<Type>();
foreach (var file in files)
{
//加载文件夹中的所有dll
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
var types = assembly.GetTypes();
foreach (var t in types)
{
if (t.GetMethod("Voice")!=null)
{
animalsTypes.Add(t);
}
}
}
//以上是加载animals程序集并把方法为Voice的类型传给animalsTypes
//下面是调用方法
while (true)
{
for (int i = 0; i < animalsTypes.Count; i++)
{
Console.WriteLine($"{i+1}.{animalsTypes[i].Name}");
}
Console.WriteLine("================");
Console.WriteLine("Please choose animal");
int index = int.Parse(Console.ReadLine());
if (index>animalsTypes.Count||index<1)
{
Console.WriteLine("No such an animal.Try again!");
continue;
}
int times = int.Parse(Console.ReadLine());//动物叫的次数
var t = animalsTypes[index - 1];//拿到动物类型
var m = t.GetMethod("Voice");//拿到Voice方法
var o = Activator.CreateInstance(t);//创建对象
m.Invoke(o, new object[] {times});
//MethodBase.Invoke(Object, Object[]):使用指定参数调用由当前实例表示的方法(Voice)或构造函数。
}
}
}
}
再写第三方程序:
using System;
namespace Animals.Lib
{
public class cat
{
public void Voice(int times)
{
for (int i = 0; i < times; i++)
{
Console.WriteLine("Meow!");
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Animals.Lib
{
public class sheep
{
public void Voice(int times)
{
for (int i = 0; i < times; i++)
{
Console.WriteLine("Baa...");
}
}
}
}
写两个举个例子,生成解决方案后,进入程序所在文件夹的bin里找到.dll文件,粘贴到主程序文件夹的bin里的Animals文件夹里,再运行主程序,就成功了。
这种方式还是不方便,比如如果第三方程序把voice的字母拼错,就没法使用了,所以我们在主程序开发一个带有接口(只能实现voice方法)的SDk供第三方开发插件,那么就方便了。
代码如下:
using System;
using System.Collections.Generic;
using System.Text;
namespace BabyStroller.SDK
{
public interface IAnimal
{
void Voice(int times);
}
}
以及:
using System;
namespace BabyStroller.SDK
{
public class UnfinishedAttribute:Attribute
{
//就是未完成的插件可以用这个特性进行标记,当sdk的提供商扫描到这个特性的时候就知道这个插件还没有完成,就会忽略它的调用
}
}
然后再在第一方程序内部引用sdk,第三方程序引用sdk的dll文件,并继承IAnimal接口
如果在cat上加一个特性[Unfinished],如:
using BabyStroller.SDK;
using System;
namespace Animals.Lib
{
[Unfinished]
public class cat:IAnimal
{
public void Voice(int times)
{
for (int i = 0; i < times; i++)
{
Console.WriteLine("Meow!");
}
}
}
}
再在第一方程序内做如下修改:
//将
if (t.GetMethod("Voice") != null)
{
animalsTypes.Add(t);
}
//改成
if (t.GetInterfaces().Contains(typeof(IAnimal)))
{
var isUnfinished = t.GetCustomAttributes(false).Any(a=>a.GetType()== typeof(UnfinishedAttribute));
if (isUnfinished) continue;
animalsTypes.Add(t);
}
//上述代码是判断第三方dll是否使用了IAnimal接口,以及特性是UnfinishedAttribute的就过滤掉
后面的调用方法也可以改成:
var m = t.GetMethod("Voice");//拿到Voice方法
var o = Activator.CreateInstance(t);//创建对象
m.Invoke(o, new object[] {times});//调用方法
// ==》改成:
var a = o as IAnimal;
a.Voice(times);