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("--------------------------------------------------------------------");
}
}
}
}