主要内容概要
1 反射调用实例方法、静态方法、重载方法 选修:调用私有方法 调用泛型方法
2 反射字段和属性,分别获取值和设置值
3 反射的好处和局限
反射反射 程序员的快乐。
反射无处不在,MVC ASP.Net ORM IOC AOP几乎所有的框架都离不开反射。
反编译工具 ILSpy
- 反编译工具不是用的反射,是一个逆向工程
- IL (中间语言)也是一种面向对象的语言,C#语言的对比,不太好阅读和编写
编译过程如图,高级语言转换成中间语言,再到机器语言。
metadata:元数据, 数据清单,只是描述了类中有什么. CLR会读metadata。
反射的过程
反射:Reflection, System.Reflection命名空间,是微软.Net Framework提供的一个帮助类库,可以读取并使用metadata
下面看一个具体的例子:
- 正常使用数据库查询的三部曲:
- 添加引用
- 创建对象
- 调用方法
具体的示例代码如下:
IDBHelper dBHelper = new SqlServerHelper();
IDBHelper dBHelper = new MySqlHelper();
dBHelper.Query();
这种写法的主要问题是:如果MySqlHelper这个具体实现类发生变化,例如操作数据库使用SQL Server,就需要修改代码。
2. 再看一下反射的具体步骤:
- 动态加载
- 获取类型
- 创建对象
- 类型转换
- 调用方法
代码实现如下:
// 1、动态加载
Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");// dll名称 默认到当前目录下查找
// 2、获取类型
Type type = assembly.GetType("Ruanmou.DB.SqlServer.SqlServerHelper");
// 3、创建对象
object oDbHelper = Activator.CreateInstance(type);
// oDbHelper.Query();// 为什么不能直接用query 编译器就不认可 c# 是强类型语言,不能Query
dynamic dDbHelper = Activator.CreateInstance(type);
dDbHelper.Query(); //dynamic 是一个动态类型,可以避开编译器的检查,运行时检查 存在安全问题
// 4、类型转换
IDBHelper iDBHelper = oDbHelper as IDBHelper; //不报错 类型不对就返回null
//5、调用方法
iDBHelper.Query();
其中,动态加载呢又有好几种方法,如下:
Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");// dll名称 默认到当前目录下查找
Assembly assembly1 = Assembly.LoadFile(@"D:\软谋教育\Ruanmou\Advanced13Encrypt\20191010Advanced13Course3Reflection\20191010Advanced13Course3Reflection\MyReflection\MyReflection\bin\Debug\Ruanmou.DB.SqlServer.dll");// 全名称= 全路径+dll名称 + 后缀
Assembly assembly2 = Assembly.LoadFrom("Ruanmou.DB.SqlServer.dll");// 全名称
Assembly assembly3 = Assembly.LoadFrom(@"D:\软谋教育\Ruanmou\Advanced13Encrypt\20191010Advanced13Course3Reflection\20191010Advanced13Course3Reflection\MyReflection\MyReflection\bin\Debug\Ruanmou.DB.SqlServer.dll");// 全名称
当我们获取到类型type之后,就可以遍历去获取metadata了。
foreach (Type type in assembly.GetTypes())
{
Console.WriteLine(type.Name);
foreach (MethodInfo method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
- 封装
至此,我们可以封装一下反射的这个过程,通过 Factory + config 代码可以不用编译发布。
//封装
public class SimlpFactory
{
private static string IDBHelperConfig = ConfigurationManager.AppSettings["IDBHelperConfig"];
private static string DllName = IDBHelperConfig.Split(',')[1];
private static string TypeName = IDBHelperConfig.Split(',')[0];
public static IDBHelper CreateInstentce()
{
Assembly assembly = Assembly.Load(DllName);
Type type = assembly.GetType(TypeName);
object oDbHelper = Activator.CreateInstance(type);
return oDbHelper as IDBHelper;
}
}
//调用
IDBHelper iDBHelper = SimlpFactory.CreateInstentce();
iDBHelper.Query();
此时,如果需要换成别的数据库实现,我们只需要修改配置文件就可以了,不需要修改代码:
<appSettings>
<add key="IDBHelperConfig" value="Ruanmou.DB.Orcale.OrcaleHelper,Ruanmou.DB.Orcale"/>
</appSettings>
这个封装的过程体现了程序的两大特性:
程序的可配置
实现类必须是事先已有的,而且在目录下面
没有写死类型,而是通过配置文件执行,反射创建的程序的可扩展
完全不修改原有代码,只是增加新的实现,copy,修改配置文件,就可以支持新功能
反射的动态加载和动态创建对象,以及配置文件的结合
- 反射的多构造函数用法
对于有多个构造函数的类,可以在创建实例的时候,指定使用哪个构造函数,如下:
object oDbHelper = Activator.CreateInstance(type);//默认无参构造函数
object oDbHelper1 = Activator.CreateInstance(type, new object[] { "杰克" });
object oDbHelper2 = Activator.CreateInstance(type, new object[] { "我是谁" });
object oDbHelper3 = Activator.CreateInstance(type, new object[] { 123 });//参数是int的构造函数
可以获取不同的构造函数,构造函数里面的参数等。
foreach (ConstructorInfo item in type.GetConstructors())
{
Console.WriteLine(item.Name);
foreach (var parameter in item.GetParameters())
{
Console.WriteLine(parameter.ParameterType);
}
}
反射破坏单例
单例模式:类,能保证在整个进程中只有一个实例。
怎么实现?
构造函数私有化,别人就无法调用。
对外公开一个静态方法,在这个方法里面去获得实例。
public sealed class Singleton
{
private static Singleton _Singleton = null;//静态实例
private Singleton()//构造函数私有化
{
Console.WriteLine("Singleton被构造");
}
static Singleton()//静态构造函数,第一次调用这个类之前就初始化,只初始化一次
{
_Singleton = new Singleton();
}
public static Singleton GetInstance()
{
return _Singleton;
}
}
这样可以发现,不管怎么实例化,得到的都是同一个实例化的类:
Singleton singleton = new Singleton();
Singleton singleton1 = Singleton.GetInstance();
Singleton singleton2 = Singleton.GetInstance();
Singleton singleton3 = Singleton.GetInstance();
Singleton singleton4 = Singleton.GetInstance();
Console.WriteLine(singleton1.Equals(singleton4));//得到的是相同的对象
再看下反射是怎么破坏单例的?
Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");
Type type = assembly.GetType("Ruanmou.DB.SqlServer.Singleton");
//这个true 就表示 nonPublic,可以调用私有方法,这样就可以被多次实例化
object oSingleton1 = Activator.CreateInstance(type, true); //完全等价于 New Singleton
object oSingleton2 = Activator.CreateInstance(type, true);
object oSingleton3 = Activator.CreateInstance(type, true);
object oSingleton4 = Activator.CreateInstance(type, true);
Console.WriteLine(oSingleton1.Equals(oSingleton4));//完全
泛型在反射中的实例化
首先有一个泛型类,如下:
public class GenericClass<T, W, X>
{
public void Show(T t, W w, X x)
{
Console.WriteLine("t.type={0},w.type={1},x.type={2}", t.GetType().Name, w.GetType().Name, x.GetType().Name);
}
}
再看看这个泛型类在反射中怎么被调用?
//泛型编译之后生成展位符 `1 `2 `3
GenericMethod genericMethod = new GenericMethod();
genericMethod.Show<int, string, DateTime>(1234,"",DateTime.Now);
Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");
Type typeNull = assembly.GetType("Ruanmou.DB.SqlServer.GenericClass");//这样找,type是null,要有占位符
Type type = assembly.GetType("Ruanmou.DB.SqlServer.GenericClass`3"); //获取一个泛型类型
//实例化需要指定类型
Type type1 = type.MakeGenericType(new Type[] { typeof(int), typeof(string), typeof(DateTime) });
object oGenericTest = Activator.CreateInstance(type1);
反射中调用方法
首先在被反射的类中有如下这些方法:
public void Show1()
{
Console.WriteLine("这里是{0}的Show1", this.GetType());
}
public void Show2(int id)
{
Console.WriteLine("这里是{0}的Show2", this.GetType());
}
public void Show3(int id, string name)//重载方法1
{
Console.WriteLine("这里是{0}的Show3", this.GetType());
}
public void Show3(string name, int id)//重载方法2
{
Console.WriteLine("这里是{0}的Show3_2", this.GetType());
}
public void Show3(int id)//重载方法3
{
Console.WriteLine("这里是{0}的Show3_3", this.GetType());
}
public void Show3(string name)//重载方法4
{
Console.WriteLine("这里是{0}的Show3_4", this.GetType());
}
public void Show3()//重载方法5
{
Console.WriteLine("这里是{0}的Show3_1", this.GetType());
}
private void Show4(string name)//私有方法
{
Console.WriteLine("这里是{0}的Show4", this.GetType());
}
public static void Show5(string name)//静态方法
{
Console.WriteLine("这里是{0}的Show5", typeof(ReflectionTest));
}
那么在反射中怎么去调用呢?
反射创建实例,根据方法名称,反射调用方法。
dll名称——类名称——方法名称,调用方法。
MVC就是靠这一招,调用Action。
例如
- http://localhost:9999/home/index 经过解析会调用HomeController类中Index方法。
浏览器输入时只告诉了服务器类名称和方法名称,肯定是反射。MVC在启动时会先加载,扫描全部的dll,找到全部的Controller存起来,用controller来匹配,dll+类型名称。
思考一下:MVC的局限性,如果Action重载,那么反射就无法区分方法。只能通过Httpget HttpPost等特性来区分。 - AOP 反射调用方法时可以在前后插入逻辑。
Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");
Type type = assembly.GetType("Ruanmou.DB.SqlServer.ReflectionTest");
object oTest = Activator.CreateInstance(type);//获得实例
// 反射调用方法
MethodInfo show1 = type.GetMethod("Show1");
show1.Invoke(oTest, new object[0]);
// 反射调用方法 ,需要参数的时候,参数类型必须和方法参数类型保持一致
MethodInfo show2 = type.GetMethod("Show2");
show2.Invoke(oTest, new object[] { 123 });
// 反射调用重载方法:
MethodInfo show3 = type.GetMethod("Show3", new Type[] { typeof(string), typeof(int) });
show3.Invoke(oTest, new object[] { "飞哥", 134 });
// 反射调用私有方法
MethodInfo show4 = type.GetMethod("Show4", BindingFlags.NonPublic | BindingFlags.CreateInstance);
show4.Invoke(oTest, new object[] { "Tenk" });
// 反射调用静态方法: 对象的实例oTest 可以传入,也可以传入null
MethodInfo show5 = type.GetMethod("Show5");
show5.Invoke(oTest, new object[] { "彭于晏" });
show5.Invoke(null, new object[] { "Korey" });
反射中调用泛型方法:
首先有如下两个类:
public class GenericMethod
{
public void Show<T, W, X>(T t, W w, X x)
{
Console.WriteLine("t.type={0},w.type={1},x.type={2}", t.GetType().Name, w.GetType().Name, x.GetType().Name);
}
}
public class GenericClass<T, W, X>
{
public void Show(T t, W w, X x)
{
Console.WriteLine("t.type={0},w.type={1},x.type={2}", t.GetType().Name, w.GetType().Name, x.GetType().Name);
}
}
普通类中调用泛型方法:
泛型方法没有占位符,在泛型类,接口中才有占位符。
Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");
Type type = assembly.GetType("Ruanmou.DB.SqlServer.GenericMethod");
object oTest = Activator.CreateInstance(type);
MethodInfo genericMethod = type.GetMethod("Show");
MethodInfo genericMethod1 = genericMethod.MakeGenericMethod(new Type[] { typeof(int), typeof(string), typeof(DateTime) });// 指定泛型方法的 具体参数
genericMethod1.Invoke(oTest, new object[] { 123, "jun", DateTime.Now }); // 传入参数必须和声明的一样
泛型类中调用泛型方法:
Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");
Type type = assembly.GetType("Ruanmou.DB.SqlServer.GenericClass`3"); //获取一个泛型类型
Type type1 = type.MakeGenericType(new Type[] { typeof(int), typeof(string), typeof(DateTime) });
object oGenericTest = Activator.CreateInstance(type1);
MethodInfo genericMethod = type1.GetMethod("Show");
genericMethod.Invoke(oGenericTest, new object[] { 123, "子玉", DateTime.Now });//要和T W X的顺序保持一致
反射中使用属性和字段
首先,有一个实体类People:
public class People
{
public People()
{
Console.WriteLine("{0}被创建", this.GetType().FullName);
}
public int Id { get; set; }
public string Name { get; set; }
public string Description;
}
正常使用的写法,这里不再赘述。
反射中使用这种实体类,首先没有必要去加载dll了,因为实体类变了的话,代码肯定是要重构的了。因此直接引用:
Type type = typeof(People);
object oPeople = Activator.CreateInstance(type);
//使用属性
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.Name.Equals("Id"))
{
prop.SetValue(oPeople, 123);
}
else if (prop.Name.Equals("Name"))
{
prop.SetValue(oPeople, "风之林");//修改属性
}
Console.WriteLine($"oPeople.{prop.Name}={prop.GetValue(oPeople)}");//读取值
}
//使用字段
foreach (FieldInfo field in type.GetFields())
{
if (field.Name.Equals("Description"))
{
field.SetValue(oPeople, ".Net learner");
}
}
- Get 反射展示是有意义的,反射遍历,可以不用改代码
- Set 感觉没啥用,但是实际还是有用的,下面通过一个数据库操作实例来体会这个作用。
数据库操作实例中应用反射
首先有几个实体类:
public class BaseModel
{
public int Id { set; get; }
}
public class Company : BaseModel
{
public string Name { get; set; }
public System.DateTime CreateTime { get; set; }
public int CreatorId { get; set; }
public int? LastModifierId { get; set; }
public DateTime? LastModifyTime { get; set; }
}
public class User : BaseModel
{
public string Name { set; get; }
public string Account { set; get; }
public string Password { set; get; }
public string Email { set; get; }
public string Mobile { set; get; }
public int CompanyId { set; get; }
}
然后在数据库帮助类中有:
这里只查询了Company,思考怎么用一个方法满足多个类型的需求?
public static Company Find(int id)
{
string sql = @"SELECT [Id]
,[Name]
,[CreateTime]
,[CreatorId]
,[LastModifierId]
,[LastModifyTime]
FROM [SystemDB].[dbo].[Company] where Id=" + id;
Type type = typeof(Company);
object oCompany = Activator.CreateInstance(type);
using (SqlConnection connection = new SqlConnection(Customers))
{
SqlCommand sqlCommand = new SqlCommand(sql, connection);
connection.Open();
SqlDataReader reader = sqlCommand.ExecuteReader();
if (reader.Read()) //开始读取
{
foreach (var prop in type.GetProperties())
{
prop.SetValue(oCompany, reader[prop.Name]);
//if (prop.Name.Equals("Id"))
//{
// prop.SetValue(oCompany, reader[prop.Name]);
//}
//else if (prop.Name.Equals("Name"))
//{
// prop.SetValue(oCompany, reader[prop.Name]);
//}
}
//Console.WriteLine(reader["Id"]);
//Console.WriteLine(reader["Name"]);
//return new Company() { Id =(int)reader["Id"] };
}
}
return (Company)oCompany;
}
下面写一个泛型方法来实现上面的Find功能,支持多种类型的查询:
public static T Find<T>(int id) where T : BaseModel
{
Type type = typeof(T);
object oObject = Activator.CreateInstance(type);//有可能是user 有可能是company
//List<string>
var propNames = type.GetProperties().Select(a => $"[{a.Name}]");
string props = string.Join(",", propNames);
string sql = $"SELECT {props } FROM [{type.Name}] where Id={id}"; //生成sql语句
using (SqlConnection connection = new SqlConnection(Customers))
{
SqlCommand sqlCommand = new SqlCommand(sql, connection);
connection.Open();
SqlDataReader reader = sqlCommand.ExecuteReader();
if (reader.Read()) //开始读取
{
foreach (var prop in type.GetProperties())
{
prop.SetValue(oObject, reader[prop.Name]);
}
}
}
return (T)oObject;
}
这里有几个注意点:
$"SELECT {props } FROM [{type.Name}] where Id={id}
这里面的[]可以避免因为关键字产生的错误- propNames 是一个list,然后用
string.Join(",", propNames)
拼接成一个字符串
反射的优缺点
优点 :反射是动态,支持良好的扩展性
- 减少对象和对象之间的依赖,只需要知道类名(字符串)、方法名(字符串),就可以调用
- 还可以突破特定权限,可以做到普通方式无法做到的
缺点
- 使用麻烦,编写比较困难,代码量大,可以封装一下
- 避开编译器检查,编写的时候容易出错
- 性能问题 性能损耗大
下面用一个实例来验证一下性能:
public static void Show()
{
Console.WriteLine("*******************Monitor*******************");
long commonTime = 0;
long reflectionTime = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 1_000_000; i++)
{
IDBHelper iDBHelper = new SqlServerHelper();
iDBHelper.Query();
}
watch.Stop();
commonTime = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
//缓存
Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");//1 动态加载
Type dbHelperType = assembly.GetType("Ruanmou.DB.SqlServer.SqlServerHelper");//2 获取类型
for (int i = 0; i < 1_000_000; i++)
{
//Assembly assembly = Assembly.Load("Ruanmou.DB.SqlServer");//1 动态加载
//Type dbHelperType = assembly.GetType("Ruanmou.DB.SqlServer.SqlServerHelper");//2 获取类型
object oDBHelper = Activator.CreateInstance(dbHelperType);//3 创建对象
IDBHelper dbHelper = (IDBHelper)oDBHelper;//4 接口强制转换
dbHelper.Query();//5 方法调用
}
watch.Stop();
reflectionTime = watch.ElapsedMilliseconds;
}
Console.WriteLine("commonTime={0} reflectionTime={1}", commonTime, reflectionTime);
}
}
经过测试: 发现反射确实性能损耗比较大 普通方式:140, 反射:34860,确实让人无法接受,经过缓存优化之后发现,普通方式:65 反射:628,对于性能损耗,要理性看待,因为执行的数量巨大,中间的这些性能损耗是可以忽略不计。
缓存优化,把dll加载和类型获取,只执行一次。
MVC, Asp.Net ORM IOC AOP都在用反射,几乎都有缓存 !
MVC ORM启动很慢是因为完成了很多初始化,反射的那些东西。后面运行就会很快了。