反射
反射的概念
-
程序是用来处理数据的,而程序本身也是数据,有关程序及其类型的数据被称为 MetaData ,保存在程序集中。程序在运行时,可以查看其他程序集或其本身 Metadata,一个运行的程序查看本身的元数据或其他程序集的元数据的行为称为反射(Reflection)。
-
通过反射,可以在运行时获得某个类型的各种信息,包括方法、属性、事件、及构造函数等,还可以获得每个成员的名称等信息
反射的特点
-
在程序运行时,动态创建对象、调用方法、设置属性和激发事件,而不是在编译时完成
反射的应用
-
在VS的智能提示、使用MSIL反汇编工具查看IL代码都用的时反射技术。
-
Java开发工具Eclipse中插件使用,也都是反射技术
开发中的应用
-
系统需要基于插件开发的时候,必须要用反射
-
在简单工厂和抽象工厂设计模式中讲使用反射技术
-
使用反射一般都要配合接口使用
-
反射技术使得系统性能一定程度降低,除非必要情况,反射不宜过多使用
什么是Type?
对于程序中用到的每个类型,CLR 都会创建一个包含这个类型信息的 Type 对象。程序中每用到一个类型都会关联到独立的 Type 类的对象。不管创建的类型有多少个实例,只有一个 Type 对象会关联到这些所有的实例。
什么是程序集?
程序集是一个可以寄宿于 CLR 中的、拥有版本号的、自解释、可配置的二进制文件,程序集的扩展名为 exe 或 dll。程序集是存放类型的集合,通过程序集可以获取程序集内所有的类型信息。
反射、DLL、IL、Metadata、Assembly
C# 编写的源代码被编译成符合 CLR 规范的中间语言 (IL)。 IL 代码和资源(如位图和字符串)存储在扩展名通常为 .dll or .exe 的程序集中。程序集包含一个介绍程序集的类型、版本和区域性的清单,这些描述性信息称为 Metadata(元数据),而反射就需要在 Assembly 中找到这些 Metadata 然后进行对象实例化、调用方法、读取属性等。
1. 程序集加载
ReflectionTest类
public class ReflectionTest
{
public ReflectionTest()
{
Console.WriteLine("This is a non-parametric constructor");
}
public ReflectionTest(string name)
{
Console.WriteLine($"This is a parametric construction , parameter:{name}");
}
//private ReflectionTest()
//{
// Console.WriteLine("This is a private constructor");
//}
}
-
Load 一般用来加载同一文件下的其他程序集 无需加dll后缀,不可以指定路径
var assembly = Assembly.Load("TestDll");
-
LoadFile 一般用来加载不再同一文件下的其他程序集 必须写上完全限定路径
var assm = Assembly.LoadFile(@"C:\Users\43789\Documents\LINQPad Queries\反射\TestDll.dll");
-
LoadFrom 一般用来加载不再同一文件下的其他程序集
var assm2 = Assembly.LoadFrom(@"C:\Users\43789\Documents\LINQPad Queries\反射\TestDll.dll");
或者放在当Dll文件在启动程序目录时var assm3 = Assembly.LoadFrom("TestDll.dll");
2. 获取程序集(dll)中的指定类型
Type type = assm2.GetType("TestDll.ReflectionTest");
3. 实例化对象
var assm = Assembly.LoadFrom(@"C:\Users\43789\Documents\LINQPad Queries\反射\TestDll.dll");
//获取类型
Type type = assm.GetType("TestDll.ReflectionTest");
//通过类型创建实例,返回值是object类型
object objTest = Activator.CreateInstance(type);//调用无参的构造函数
//调用有参数的构造函数 参数以object数组传递进来
object objTest2 = Activator.CreateInstance(type, new object[]{"Test"});
Type typeTest = typeof(Test);
//调用私有化的构造函数 第二个参数 bool nonPublic 选择true
object obj = Activator.CreateInstance(typeTest, true);
public class Test
{
private Test()
{
Console.WriteLine("This is a private constructor");
}
}
4. 获取构造函数和构造函数的参数
var assm = Assembly.LoadFrom(@"C:\Users\43789\Documents\LINQPad Queries\反射\TestDll.dll");
// 获取当前dll文件里所有的类类型
assm.GetTypes()
.ToList()
.ForEach(t=> t.Dump());
//获取ReflectionTest类里所有的构造函数并打印
assm.GetType("TestDll.ReflectionTest")
.GetConstructors()
.ToList()
.ForEach(ctor=>ctor.Dump());
//获取所有构造函数里的参数
foreach (var ctor in assm.GetType("TestDll.ReflectionTest").GetConstructors())
{
foreach (var pi in ctor.GetParameters())
pi.Dump();
}
assm
.GetType("TestDll.ReflectionTest")
.GetConstructors()
.ToList()
.ForEach(ctor => ctor.GetParameters().ToList().ForEach(pi => pi.Dump()));
5.反射调用方法
Test类
public class Test
{
public void Foo1()
{
Console.WriteLine($"这是一个普通方法 方法名: {nameof(Foo1)}" );
}
public void Foo2(int i)
{
Console.WriteLine($"这是一个有参数的方法 方法名: {nameof(Foo1)}, 参数是: {i.ToString()}");
}
public int Add(int a, int b)
{
Console.WriteLine($"调用有返回值的方法,方法名:{nameof(Add)} 接收返回值: {a + b}");
return a + b;
}
public static void StaticFoo()
{
Console.WriteLine($"这是一个静态方法 方法名: {nameof(StaticFoo)}");
}
private void PrivateFoo()
{
Console.WriteLine($"这是一个私有方法 方法名: {nameof(PrivateFoo)}");
}
public void GenericFoo1<T1,T2>(T1 t1, T2 t2)
{
Console.WriteLine($"这是一个泛型方法 方法名:{nameof(GenericFoo1)}, 参数类型 {typeof(T1)},{typeof(T2)},参数 {t1},{t2}");
}
}
实现方法
var assm = Assembly.LoadFrom(@"C:\Users\43789\Documents\LINQPad Queries\反射\TestDll.dll");
Type testType = assm.GetType("TestDll.Test")
// 通过方法名获取方法
var foo1 = testType.GetMethod("Foo1");
object objTest = Activator.CreateInstance(testType);
//调用方法,需要两个参数,一个实例对象,一个方法参数
foo1.Invoke(objTest, null); //无参数可以选择null
foo1.Invoke(objTest, new object[] {});//或者传入空数组
//调用有参数的方法
var foo2 = testType.GetMethod("Foo2");
foo2.Invoke(objTest, new object[] {5});
// 调用有返回值的方法
var add = testType.GetMethod("Add");
var res = add.Invoke(objTest, new object[] { 3, 5 });
$"返回值{res}".Dump();
// 调用静态方法
var staticFoo = testType.GetMethod("StaticFoo");
// 因为静态方法不需要创建类的实例,所以调用的时候第一个参数可以填null
staticFoo.Invoke(null,null);
// 调用私有方法
// 因为方法是私有的且是实例方法 所以第二个参数要选择BindingFlags.NonPublic 和 BindingFlags.Instance
var privateFoo = testType.GetMethod("PrivateFoo", BindingFlags.NonPublic| BindingFlags.Instance);
privateFoo.Invoke(objTest,null);
// 调用泛型方法
var genericFoo = testType.GetMethod("GenericFoo1");
// 设定泛型方法的参数并返回一个新的方法 参数用类型数组设置
var newFoo = genericFoo.MakeGenericMethod(new Type[] { typeof(int), typeof(string) });
newFoo.Invoke(objTest, new object[] {5,"str"});
6.反射调用泛型类
GenericClass
public class GenericClass<T>
{
public void GenericFoo<S>(S s1)
{
Console.WriteLine($"泛型类{nameof(GenericClass<T>)}+泛型方法{nameof(GenericFoo)}调用 泛型类类型:{typeof(T)}, 泛型方法参数类型:{typeof(S)}, 泛型方法传入参数{s1}");
}
}
实现方法
var assm = Assembly.LoadFrom(@"C:\Users\43789\Documents\LINQPad Queries\反射\TestDll.dll");
//assm.GetType("xxx`n"); n代表泛型参数的个数 //创建泛型的类型,传入泛型类型string
Type t = assm.GetType("TestDll.GenericClass`1").MakeGenericType(new Type[] {typeof(double)});
t.Dump();
//泛型方法
var genericFoo = t.GetMethod("GenericFoo").MakeGenericMethod(new Type[]{typeof(string)});
genericFoo.Dump();
//创建实例
object instance = Activator.CreateInstance(t);
instance.Dump();
//调用
genericFoo.Invoke(instance, new object[] {"123"});
7. 反射调用属性和字段
PropertyClass
public class PropertyClass
{
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public int id;
public string name;
public string phone;
}
实现方法
Assembly assembly = Assembly.LoadFrom(@"C:\Users\43789\Documents\LINQPad Queries\反射\TestDll.dll");
Type type = assembly.GetType("TestDll.PropertyClass");
object instance = Activator.CreateInstance(type);
foreach (var prop in type.GetProperties())
{
if (prop.Name == "Id")
prop.SetValue(instance, 1);
if (prop.Name == "Name")
prop.SetValue(instance, "张三");
Console.WriteLine(prop.GetValue(instance));
}
foreach (var field in type.GetFields())
{
if (field.Name.Equals("id"))
{
field.SetValue(instance, 1);
Console.WriteLine(field.GetValue(instance));
}
else if (field.Name.Equals("name"))
{
field.SetValue(instance, "张三");
Console.WriteLine(field.GetValue(instance));
}
}
8.反射的简单应用
// 下面对 entity 承载的数据进行了实例化,并且依次赋值个了 entityDto,这种是常规写法。
Product product = new Product
{
ID = 1,
Name = "张三"
};
ProductDto productDto = new ProductDto();
productDto.ID = product.ID;
productDto.Name = product.Name;
// 如果字段多了呢,还需要手动依次赋值吗?
// 我们可以使用反射,对 entity entityDto 之间进行自动赋值,提高开发效率。
Product product1 = new Product
{
ID = 1,
Name = "张三"
};
// 获取Product类型
Type productType = typeof(Product);
Type productDtoType = typeof(ProductDto);
// 根据类型创建实例
object productDto1 = Activator.CreateInstance(productDtoType);
foreach (var prop in productDtoType.GetProperties())
{
// 依次拿取 dto 属性名称,在 Product Type 查找,并且从 Product Instance 获取值
object val = productType.GetProperty(prop.Name).GetValue(product1);
// ProductDto Instance Set Propertie Val
prop.SetValue(productDto1, val);
}
public class Product
{
public int ID { get; set; }
public String Name { get; set; }
}
public class ProductDto
{
public int ID { get; set; }
public String Name { get; set; }
}
9. 使用反射的方式实现计算器
接口模块
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICal
{
public interface ICalculator
{
/// <summary>
/// 加法接口
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
int Add(int a, int b);
/// <summary>
/// 减法接口
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
int Sub(int a, int b);
}
}
算法模块
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICal;//添加接口程序集引用
namespace CalDll
{
public class Calculator : ICal.ICalculator
{
//实现接口的加法
public int Add(int a, int b) => a + b;
//实现接口的减法
public int Sub(int a, int b) => a - b;
}
}
窗体实现
窗体类
using System;
using System.Windows.Forms;
using System.Reflection;//引入反射命名空间
using ICal;// 添加ICal 接口引用
// 不需要添加CalDll的引用
namespace CalculatorByReflection
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnAdd_Click(object sender, EventArgs e)
{
//从本地加载程序集[CalDLL.dll],从此程序集中找到指定的类型然后使用系统激活器创建它的实例
//Assembly 表示程序集
//LoadFrom 加载程序集 参数要写上程序集的具体名称 当前是从CalDll程序集的类来实现对象的具体方法
//CreateInstance 创建类的实例 参数是类的完全限定名 即命名空间+类名
//CreateInstance 返回的是object类型 需要加上强制转换
//所谓反射就是从你要创建类所在的程序集和类的完全限定名所表示
ICalculator calc = (ICalculator)Assembly.LoadFrom("CalDll.Dll").CreateInstance("CalDll.Calculator");
lblSum.Text = calc.Add(Convert.ToInt32(txtNum1), Convert.ToInt32(txtNum2)).ToString();
}
}
}
接口类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICal
{
public interface ICalculator
{
/// <summary>
/// 加法接口
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
int Add(int a, int b);
/// <summary>
/// 减法接口
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
int Sub(int a, int b);
}
}
接口实现类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICal;//添加接口程序集引用
namespace CalDll
{
public class Calculator : ICal.ICalculator
{
//实现接口的加法
public int Add(int a, int b) => a + b;
//实现接口的减法
public int Sub(int a, int b) => a - b;
}
}
启动程序后如果抛文件未找到异常
需要手动复制CalDll程序集的dll文件到主程序debug目录
-
Assembly 表示程序集
-
LoadFrom 加载程序集 参数要写上程序集的具体名称 当前是从CalDll程序集的类来实现对象的具体方法
-
CreateInstance 创建类的实例 参数是类的完全限定名 即命名空间+类名
使用反射后,不需要添加CalDll的引用,降低模块之间的耦合
10.使用反射改进简单工厂
接口类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleFactoryByReflectionPro
{
public interface IReport
{
void Report();
}
}
实现接口类
using System.Windows.Forms;
// ExcelReport
namespace SimpleFactoryByReflectionPro
{
public class ExcelReport : IReport
{
public void Report()
{
MessageBox.Show("...正在使用Excel打印报表");
}
}
}
using System.Windows.Forms;
// WordReport
namespace SimpleFactoryByReflectionPro
{
internal class WordReport : IReport
{
public void Report()
{
MessageBox.Show("...正在使用Word打印报表");
}
}
}
工厂类
using System.Configuration;//添加引用
using System.Reflection;
namespace SimpleFactoryByReflectionPro
{
public static class Factory
{
// 1. 读取配置文件
static string reportType = ConfigurationManager.AppSettings["ReportType"].ToString();
// 2.使用反射创建实现接口类的对象并以接口类型返回
// 通过给定程序集[SimpleFactoryByReflectionPro],从此程序集中找到指定的类型然后使用系统激活器创建它的实例
// 即创建SimpleFactoryByReflectionPro.ExcelReport类对象
public static IReport GetReport()
{
return (IReport)Assembly.Load("SimpleFactoryByReflectionPro").CreateInstance("SimpleFactoryByReflectionPro." + reportType);
}
}
}
使用反射改进的优点
使用时不需要修改代码
标签:反射,int,程序,System,using,public From: https://www.cnblogs.com/Honsen/p/18210033