1、为什么学习反射?
反射其实无处不在,ORM/MVC/IOC等框架都用到了反射。
从高级语言到机器码的整个编译运行过程中,反射实际上就是在编译之后的DLL/EXE文件上起作用,DLL/EXE文件中又包含metadata文件和IL文件。
IL文件是对标于C#代码的代码,不太好阅读。
IL编码可通过ILSpy应用进行读取,ILSpy是一个逆向应用,可以把我们所写的各种高级语言的单词编译后生成的DLL/EXE文件反编译回来。
metadata是一个清单数据,就是元数据。
2、什么是反射?
反射是一个工具,是微软提供的一个帮助类库,可以读取并使用metadata文件,命名空间是System.Reflection。
3、反射创建对象调用方法一般步骤
- 引入System.Reflection命名空间
- 动态加载
- 获取类型
- 创建对象
- 类型转换
- 调用方法
具体方法如下代码所示:
/// <summary>
/// 通过反射,创建对象、调用方法
/// </summary>
public static void ShowReflection()
{
Console.WriteLine("------------------------------------ShowReflection-------------------------------------");
//1.引入System.Reflection
//2.动态加载
//方式一:对应项目的DLL文件名,不加后缀名,一般不推荐使用
Assembly assembly1 = Assembly.Load("Reflection.SqlServer");
//方式二:对应项目的DLL文件名,需要后缀名,推荐使用该方式
Assembly assembly2 = Assembly.LoadFrom("Reflection.SqlServer.dll");
//方式三:对应项目DLL文件的全路径,需要后缀名
Assembly assembly3 = Assembly.LoadFrom(@"G:\study\.NET\高级班14期\01ExerciseProject\Reflection\Reflection04\Reflection04\bin\Debug\netcoreapp3.1\Reflection.SqlServer.dll");
//方式四:对应项目DLL文件的全路径,需要后缀名
Assembly assembly4 = Assembly.LoadFile(@"G:\study\.NET\高级班14期\01ExerciseProject\Reflection\Reflection04\Reflection04\bin\Debug\netcoreapp3.1\Reflection.SqlServer.dll");
//3.获取类型
//需要类型的全类名,全类名=命名空间+类名
Type type = assembly2.GetType("Reflection.SqlServer.SqlServerHelper");
//4.创建对象
//方式一:如果为空时,会报异常
object obj = Activator.CreateInstance(type);
//因为类型是object声明的,不能调用SqlServerHelper类型声明的Query(string sPar)方法
//在C#中,变量的声明是编译时决定的
//方式二:动态类型,dynamic是运行时决定的,可以避开编译器的检查
dynamic dmc = Activator.CreateInstance(type);
dmc.Query("dynamic ShowReflection");
//dmc.Show();
//dmc调用Show()方法在编译时不会报错,这是因为dynamic能够避开编译器的检查
//但是由于SqlServerHelper类中没有定义Show()方法,因此dmc在调用Show()方法时会报错
//Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
//“'DB.SqlServer.MySqlHelper' does not contain a definition for 'Show'”
//5.类型转换
//方式一:强制转换,如果实际类型不一致会报错
IDBHelper dbHelper1 = (IDBHelper)obj;
//方式二:
IDBHelper dbHelper2 = obj as IDBHelper;
//6.调用方法
dbHelper1.Query("强制转换方式 ShowReflection");
dbHelper2.Query("as方式 ShowReflection");
}
4、封装反射操作,实现细节解耦
public class SimpleFactory
{
public static T CreateInstance<T>() where T : class
{
string parStr = ReadConfig.GetConfig("ReflectionConfig");
string[] pars = parStr.Split(',');
string dllName = pars[1];
string typeName = pars[0];
Assembly assembly = Assembly.LoadFrom(dllName);
Type type = assembly.GetType(typeName);
object obj = Activator.CreateInstance(type);
return obj as T;
}
}
5、性能对比
/// <summary>
/// 反射的局限性
/// 性能比较差
///
/// 反射中最耗时的操作是动态加载,读取dll文件,Assembly.LoadFrom(dll文件名,需要加载后缀名)
/// 其次的耗时操作是获取类型,assembly.GetType(全类名=命名空间+类名)
///
/// 反射确实会存在性能问题
/// 但要扬长避短,反射的优点还是显而易见的,许多地方目前根本离不开反射
/// 经过优化之后,其实性能没有想象的那么差
/// </summary>
public static void PerComp()
{
long commonTime = 0;
long reflectTime = 0;
long optimitTime = 0;
Stopwatch commonWatch = new Stopwatch();
Stopwatch reflectWatch = new Stopwatch();
Stopwatch optimitWatch = new Stopwatch();
commonWatch.Start();
for(int i = 0; i < 1000_000; i++)
{
OracleHelper helper = new OracleHelper();
helper.Query("");
}
commonWatch.Stop();
commonTime = commonWatch.ElapsedMilliseconds;
reflectWatch.Start();
for(int i = 0; i < 1000_000; i++)
{
Assembly assembly1 = Assembly.LoadFrom("Reflection.Oracle.dll");
Type type1 = assembly1.GetType("Reflection.Oracle.OracleHelper");
IDBHelper helper1 = Activator.CreateInstance(type1) as IDBHelper;
helper1.Query("");
}
reflectWatch.Stop();
reflectTime = reflectWatch.ElapsedMilliseconds;
optimitWatch.Start();
Assembly assembly2 = Assembly.LoadFrom("Reflection.Oracle.dll");
Type type2 = assembly2.GetType("Reflection.Oracle.OracleHelper");
for(int i = 0; i < 1000_000; i++)
{
IDBHelper helper2 = Activator.CreateInstance(type2) as IDBHelper;
helper2.Query("");
}
optimitWatch.Stop();
optimitTime = optimitWatch.ElapsedMilliseconds;
Console.WriteLine($"普通方法耗时:{commonTime},反射方法耗时:{reflectTime},优化方法耗时:{optimitTime}");
}
6、通过反射创建对象、调用方法
实例:
public class FuncAndMethod
{
#region 构造函数
/// <summary>
/// 无参构造函数
/// </summary>
public FuncAndMethod()
{
Console.WriteLine($"{this.GetType().Name}的无参构造函数");
}
public FuncAndMethod(int iPar)
{
Console.WriteLine($"{this.GetType().Name}的单参构造函数,参数类型:{iPar.GetType().Name},参数:{iPar}");
}
public FuncAndMethod(string sPar)
{
Console.WriteLine($"{this.GetType().Name}的单参构造函数,参数类型:{sPar.GetType().Name},参数:{sPar}");
}
public FuncAndMethod(int iPar,string sPar)
{
Console.WriteLine($"{this.GetType().Name}的双参构造函数,参数类型:{iPar.GetType().Name}、{sPar.GetType().Name}," +
$"参数:{iPar}、{sPar}");
}
private FuncAndMethod(string sPar, int iPar)
{
Console.WriteLine($"{this.GetType().Name}的私有化双参构造函数,参数类型:{sPar.GetType().Name}、{iPar.GetType().Name}," +
$"参数:{sPar}、{iPar}");
}
#endregion
#region 方法
/// <summary>
/// 无参方法
/// </summary>
public void ShowNoPar()
{
Console.WriteLine($"{this.GetType().Name}的无参方法,ShowNowPar()");
}
public void ShowSinglePar(string par)
{
Console.WriteLine($"{this.GetType().Name}的无参方法,参数:{par},参数类型:{par.GetType().Name}");
}
public void ShowDoublePar(string sPar, int iPar)
{
Console.WriteLine($"{this.GetType().Name}的单参方法,参数:{sPar}、{iPar},参数类型:{sPar.GetType().Name}、{iPar.GetType().Name}");
}
/// <summary>
/// 重载方法一
/// </summary>
public void ShowOverLoad()
{
Console.WriteLine($"{this.GetType().Name}的重载方法,无参");
}
/// <summary>
/// 重载方法二
/// </summary>
/// <param name="iPar"></param>
public void ShowOverLoad(int iPar)
{
Console.WriteLine($"{this.GetType().Name}的重载方法,参数:{iPar},参数类型:{iPar.GetType().Name}");
}
/// <summary>
/// 重载方法三
/// </summary>
/// <param name="sPar"></param>
public void ShowOverLoad(string sPar)
{
Console.WriteLine($"{this.GetType().Name}的重载方法,参数:{sPar},参数类型:{sPar.GetType().Name}");
}
/// <summary>
/// 重载方法四
/// </summary>
/// <param name="sPar"></param>
/// <param name="iPar"></param>
public void ShowOverLoad(string sPar, int iPar)
{
Console.WriteLine($"{this.GetType().Name}的重载方法,参数:{sPar}、{iPar},参数类型:{sPar.GetType().Name}、{iPar.GetType().Name}");
}
/// <summary>
/// 重载方法五
/// </summary>
/// <param name="iPar"></param>
/// <param name="sPar"></param>
public void ShowOverLoad(int iPar, string sPar)
{
Console.WriteLine($"{this.GetType().Name}的重载方法,参数:{iPar}、{sPar},参数类型:{iPar.GetType().Name}、{sPar.GetType().Name}");
}
private void ShowPrivate(string sPar)
{
Console.WriteLine($"{this.GetType().Name}的私有方法,参数:{sPar},参数类型:{sPar.GetType().Name}");
}
public static void ShowStatic(string sPar)
{
Console.WriteLine($"静态方法,参数:{sPar},参数类型:{sPar.GetType().Name}");
}
#endregion
}
反射获取对象、调用方法:
/// <summary>
/// 反射创建泛型对象,调用泛型方法
/// </summary>
public static void RefCallCommon()
{
Console.WriteLine();
Console.WriteLine("------------------------------根据不同的构造函数创建对象---------------------------------");
Assembly assembly1 = Assembly.LoadFrom("Reflection04.dll");
Type type1 = assembly1.GetType("Reflection04.BasicKnowledge.FuncAndMethod");
//无参构造函数
object obj1 = Activator.CreateInstance(type1);
object obj1_1 = Activator.CreateInstance(type1, new object[0]);
//单参构造函数
object obj2 = Activator.CreateInstance(type1, new object[] { 157 });
object obj3 = Activator.CreateInstance(type1, new object[] { "单参构造函数" });
//双参构造函数
object obj5 = Activator.CreateInstance(type1, new object[] { 135, "双参构造函数" });
//私有化双参构造函数,没弄明白
//object obj6 = Activator.CreateInstance(type1, true);
Console.WriteLine();
Console.WriteLine("------------------------------调用不同的方法---------------------------------");
Assembly assembly2 = Assembly.LoadFrom("Reflection04.dll");
Type type2 = assembly2.GetType("Reflection04.BasicKnowledge.FuncAndMethod");
object obj7 = Activator.CreateInstance(type2);
//无参方法
MethodInfo showNoPar1 = type2.GetMethod("ShowNoPar");
MethodInfo showNoPar2 = type2.GetMethod("ShowNoPar", new Type[0]);
showNoPar1.Invoke(obj7, new object[0]);
showNoPar2.Invoke(obj7, new object[0]);
//单参方法
MethodInfo showSinglePar = type2.GetMethod("ShowSinglePar", new Type[] { typeof(string) });
showSinglePar.Invoke(obj7, new object[] { "单参方法" });
//双参方法
MethodInfo showDoublePar = type2.GetMethod("ShowDoublePar", new Type[] { typeof(string), typeof(int) });
showDoublePar.Invoke(obj7, new object[] { "双参方法", 722 });
//重载方法
MethodInfo showOverLoad1 = type2.GetMethod("ShowOverLoad", new Type[0]);
showOverLoad1.Invoke(obj7, new object[0]);
MethodInfo showOverLoad2 = type2.GetMethod("ShowOverLoad", new Type[] { typeof(int) });
showOverLoad2.Invoke(obj7, new object[] { 942 });
MethodInfo showOverLoad3 = type2.GetMethod("ShowOverLoad", new Type[] { typeof(string) });
showOverLoad3.Invoke(obj7, new object[] { "单参string方法" });
MethodInfo showOverLoad4 = type2.GetMethod("ShowOverLoad", new Type[] { typeof(string), typeof(int) });
showOverLoad4.Invoke(obj7, new object[] { "双参方法string、int", 753 });
MethodInfo showOverLoad5 = type2.GetMethod("ShowOverLoad", new Type[] { typeof(int), typeof(string) });
showOverLoad5.Invoke(obj7, new object[] { 943, "双参方法int、string" });
//调用私有化方法
MethodInfo showPrivate = type2.GetMethod("ShowPrivate", BindingFlags.NonPublic | BindingFlags.Instance);
showPrivate.Invoke(obj7, new object[] { "私有化方法" });
//调用静态方法
MethodInfo showStatic = type2.GetMethod("ShowStatic", BindingFlags.Static | BindingFlags.Public);
showStatic.Invoke(obj7, new object[] { "静态方法" });
}
7、反射调用属性、字段
public class People
{
public People()
{
Console.WriteLine($"{this.GetType().Name}被创建");
}
public int Id { get; set; }
public string Name { get; set; }
public string Description;
}
/// <summary>
/// 反射调用对象的属性、字段
/// </summary>
public static void RefCallPropField()
{
Console.WriteLine("-----------------------------反射获取属性、字段--------------------------------");
//属性:由get、set方法,本质上属性并不能存储值,存储值是由字段完成的,只是属性对应的字段我们并没有显式地写出来
//字段:没有get、set方法
Assembly assembly1 = Assembly.LoadFrom("Reflection.Model.dll");
Type type1 = assembly1.GetType("Reflection.Model.People");
object oResult = Activator.CreateInstance(type1);
//遍历对象属性并赋值
foreach(PropertyInfo prop in type1.GetProperties())
{
if (prop.Name.Equals("Id"))
prop.SetValue(oResult, 138);
if (prop.Name.Equals("Name"))
prop.SetValue(oResult, "超超");
Console.WriteLine($"{type1.Name}的属性:{prop.Name}");
}
Console.WriteLine();
foreach(PropertyInfo prop in type1.GetProperties())
{
Console.WriteLine($"{type1.Name}的属性:{prop},属性值:{prop.GetValue(oResult)}");
}
Console.WriteLine();
foreach(FieldInfo fieldInfo in type1.GetFields())
{
if (fieldInfo.Name.Equals("Description"))
fieldInfo.SetValue(oResult, "小舍得最萌人物");
Console.WriteLine($"{type1.Name}的属性:{fieldInfo.Name}");
}
Console.WriteLine();
foreach(FieldInfo fieldInfo in type1.GetFields())
{
Console.WriteLine($"{type1.Name}的字段:{fieldInfo.Name},字段值:{fieldInfo.GetValue(oResult)}");
}
}
8、反射创建泛型对象,调用泛型方法
public class GenericClass<U,V,W>
{
public void Show(U u,V v,W w)
{
Console.WriteLine($"{this.GetType().Name}的Show()方法,参数:{u}、{v}、{w},参数类型:{typeof(U)}、{typeof(V)}、{typeof(W)}");
}
}
public class GenericMethod
{
public void Show<U,V,W>(U u,V v,W w)
{
Console.WriteLine($"{this.GetType().Name}的Show()方法,参数:{u}、{v}、{w},参数类型:{typeof(U)}、{typeof(V)}、{typeof(W)}");
}
}
public class GenericComplex<U>
{
public void Show<V,W>(U u,V v,W w)
{
Console.WriteLine($"{this.GetType().Name}的Show()方法,参数:{u}、{v}、{w},参数类型:{typeof(U)}、{typeof(V)}、{typeof(W)}");
}
}
/// <summary>
/// 反射创建泛型对象,调用泛型方法
/// </summary>
public static void ShowGeneric()
{
Console.WriteLine("------------------------------情况一:泛型类型只位于类名上---------------------------------");
Assembly assembly1 = Assembly.LoadFrom("Reflection04.dll");
//获取泛型类型时,全类名=命名空间+类名+飘点`n
Type type1 = assembly1.GetType("Reflection04.BasicKnowledge.GenericClass`3");
Type type1_1 = type1.MakeGenericType(new Type[] { typeof(int), typeof(string), typeof(DateTime) });
object oResult1 = Activator.CreateInstance(type1_1);
MethodInfo methodInfo1 = type1_1.GetMethod("Show");
methodInfo1.Invoke(oResult1, new object[] { 843, "田雨岚", DateTime.Now });
Console.WriteLine();
Console.WriteLine("------------------------------情况二:泛型类型只位于方法名上---------------------------------");
Assembly assembly2 = Assembly.LoadFrom("Reflection04.dll");
Type type2 = assembly2.GetType("Reflection04.BasicKnowledge.GenericMethod");
object oResult2 = Activator.CreateInstance(type2);
MethodInfo methodInfo2 = type2.GetMethod("Show");
MethodInfo methodInfo2_2 = methodInfo2.MakeGenericMethod(new Type[] { typeof(string), typeof(double), typeof(int) });
methodInfo2_2.Invoke(oResult2, new object[] { "小舍得", 37.215, 953 });
Console.WriteLine();
Console.WriteLine("------------------------------情况三:泛型类型既位于类名又位于方法名上---------------------------------");
Assembly assembly3 = Assembly.LoadFrom("Reflection04.dll");
Type type3 = assembly3.GetType("Reflection04.BasicKnowledge.GenericComplex`1");
Type type3_1 = type3.MakeGenericType(new Type[] { typeof(DateTime) });
object oResult3 = Activator.CreateInstance(type3_1);
MethodInfo methodInfo3 = type3_1.GetMethod("Show");
MethodInfo methodInfo3_1 = methodInfo3.MakeGenericMethod(new Type[] { typeof(int), typeof(string) });
methodInfo3_1.Invoke(oResult3, new object[] { DateTime.Now, 834, "南峰" });
}
9、极简ORM框架
public interface IDBHelper
{
T Query<T>(int key) where T : BaseModel;
}
public class SqlServerHelper : IDBHelper
{
public T Query<T>(int key) where T : BaseModel
{
Type type = typeof(T);
object oResult = Activator.CreateInstance(type);
var propArr = type.GetProperties().Select(p => $"[{p.Name}]").ToArray();
string props = string.Join(',', propArr);
string sql = $"select {props} from [{type.Name}] where Id = {key}";
string sqlStr = ReadConfig.GetConfig("SqlServerConfig");
using (SqlConnection conn = new SqlConnection(sqlStr))
{
SqlCommand cmd = new SqlCommand(sql, conn);
conn.Open();
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
if (reader.Read())
{
foreach(PropertyInfo prop in type.GetProperties())
{
prop.SetValue(oResult, reader[prop.Name]);
}
}
conn.Close();
}
return oResult as T;
}
}
/// <summary>
/// 读取配置文件
/// 1.引入①Microsoft.Extension.Configuration; ②Microsoft.Extension.Configuration.Json
/// 2.将配置文件appsettings.json的属性设置为始终复制
/// </summary>
public class ReadConfig
{
public static string GetConfig(string key)
{
IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
IConfigurationRoot configurationRoot = builder.Build();
string config = configurationRoot["ConnectionStrs:" + key];
return config;
}
}
需要注意的是,这样写的前提是,类的属性名必须与数据库里的字段名完全一致。