C Sharp/特性与反射
反射
编辑反射是.NET Framework 的功能,并不是C#语言特有的功能。可理解为:给一个对象,在不用new操作符的情况下,也不知道静态类型的情况下,创建一个同类型的对象还能访问这个对象的各个成员。单元测试、依赖注入、泛型编程都是基于反射机制。一般情况下,使用反射时并不能感觉到,因为大多数使用的都是封装好的反射。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace eg1Part1
{
class Program
{
static void Main(string[] args)
{
ITank tank = new MediumTank();
//=================分割线===========================
//分割线以下不再用静态类型,而是动态的获取类信息并实例化
var t = tank.GetType();
object o = Activator.CreateInstance(t);
MethodInfo fireMi = t.GetMethod("Fire");
MethodInfo runMi = t.GetMethod("Run");
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();
}
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("[Light]Boom!");
}
public void Run()
{
Console.WriteLine("[Light]KA!");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("[Medium]Boom!Boom!");
}
public void Run()
{
Console.WriteLine("[Medium]KA!KA!");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("[Heavy]Boom!Boom!Boom!");
}
public void Run()
{
Console.WriteLine("[Heavy]KA!KA!KA!");
}
}
}
使用反射需要引用命名空间【using System.reflection】,这个命名空间包含如下的类:
- 使用【Assembly】定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例
- 使用【Module】了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法
- 使用【MemberInfo】
- 使用【ConstructorInfo】了解构造函数的名称、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等
- 使用【MethodInfo】了解方法的名称、返回类型、参数、访问修饰符(如pulic 或private)和实现详细信息(如abstract或virtual)等
- 使用【FiedInfo】了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值
- 使用【EventInfo】了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序
- 使用【PropertyInfo】了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值
- 使用【ParameterInfo】了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等
- System.Type
获取反射类型的三种方式:
- 通过Typeof获取某个值的类型 System.Type type = typeof(myClassName);
- 通过GetType获取对象类型 System.Type type1 = myInstance.GetType();
- 通过GetType获取类名称类型 System.Type type = System.Type.GetType("myNameSpace.myClassName");
使用System.Activator.CreateInstance方法创建指定类型的实例,并可为实例的数据成员赋值。
依赖注入
编辑依赖反转原则(DIP)是一个概念/设计原则。依赖注入(DI)是在依赖反转概念基础上,结合接口、反射机制形成的应用。
把各种类、接口放到容器中,就是“注入”。注入时还可以设置是每次创建一个新对象,还是创建一个单例模式。需要服务的实例,向容器要实例即可。
服务(功能)容器也称为Service Provider。
一般情况下,主体程序都会发布包含程序开发接口(API)的程序开发包(SDK),使用SDK中的API开发插件就会比较容易、高效。API里不一定都是接口,也有可能是一组方法,一组类,一组接口。依赖注入的高自由度意味着错误率提高,例如调方法时大小写写错,就无法找到对应的方法再成功调用了。开发插件过程中,为了避免自由度过高导致的错误,需要有一定的约束,就是SDK中的API。
首先需要用Manage Nuget增加并引用命名空间Microsoft.Extensions.DependencyInjection。然后:
//#####基础用法#####
var sc = new ServiceCollection();
sc.AddScoped(typeof(ITank),typeof(HeavyTank)); //括号中参数的含义:接口,类实现了此接口的类;Typeof代表动态的拿到这个类的信息
var sp = sc.BuildServiceProvider();
//以上为注册(把东西放进容器),ServiceCollection就是Container
//以下是从容器中“要对象”(只要能够看到Service Provider的地方都可以这么用)
ITank tank = sp.GetService<ITank>();
tank.Fire();
tank.Run();
更复杂的功能:
//#####进阶用法#####
var sc = new ServiceCollection();
sc.AddScoped(typeof(ITank), typeof(HeavyTank));
sc.AddScoped(typeof(IVehicle), typeof(Truck));
sc.AddScoped<Driver>();//Driver的构造函数参数自动从容器包含的实例中填入
var sp = sc.BuildServiceProvider();
//==================分割线===================================
var driver = sp.GetService<Driver>();
driver.Drive();
加载插件的例子
编辑using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Loader;
namespace BabyStroller.app
{
class Program
{
static void Main(string[] args)
{
//Console.WriteLine(Environment.CurrentDirectory);
//先输出这个路径,找到它,在此处新建一个Animal文件夹用来放插件(dll文件)
var folder = Path.Combine(Environment.CurrentDirectory, "Animals");
var files = Directory.GetFiles(folder);
var animalTypes = new List<Type>();
foreach (var file in files)//例如,该文件夹下有2个dll文件,每个含2个classes,每个class有一个Voice方法
{
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
var types = assembly.GetTypes();
foreach (var t in types)
{
if (t.GetMethod("Voice") != null)
{
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 input an animal's sequence number:");
int index = int.Parse(Console.ReadLine());
if (index>animalTypes.Count || index<1)
{
Console.WriteLine("No such an animal,Please 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 });
Console.WriteLine("--------------------------------------------------------------------");
Console.WriteLine("--------------------------------------------------------------------");
}
}
}
}
更好的办法是,主体软件的sdk代码中有IAnimal接口和Unfinished类(派生于Attribute)。主体软件直接引用该sdk。每个服务的类都要实现IAnimal接口且没有被[Unfinished]修饰
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Loader;
using System.Linq;
using BabyStroller.sdk;
namespace BabyStroller.app
{
class Program
{
static void Main(string[] args)
{
//Console.WriteLine(Environment.CurrentDirectory);
//先可以先输出这个路径,找到它然后建一个Animal文件夹
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)
{
//有了SDK就不用以下的操作了
//if (t.GetMethod("Voice") != null)
//{
// animalTypes.Add(t);
//}
if (t.GetInterfaces().Contains(typeof(IAnimal))) ;
{
var isUnfinished = t.GetCustomAttributes(false).Any(a => a.GetType() == typeof(UnfinishedAttribute));
if (isUnfinished)
{
continue;
}
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 input an animal's sequence number:");
int index = int.Parse(Console.ReadLine());
if (index>animalTypes.Count || index<1)
{
Console.WriteLine("No such an animal,Please 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);
Console.WriteLine("--------------------------------------------------------------------");
Console.WriteLine("--------------------------------------------------------------------");
}
}
}
}