目录
什么是反射
反射是指在运行时动态的检查、探索和修改程序集中的类型信息和成员信息的能力。反射允许程序在运行时获取程序集的元数据,包括类型、属性、方法,并可以动态的创建对象,调用方法、获取或设置属性等操作,而无需在编译时知道这些类型的具体信息。
反射的使用场景
类型检查和元数据访问
这一类应用涉及到在运行时获取类型的信息,如类的名称、方法、属性、字段等。通过元数据访问,程序可以动态的获取和操作类型信息,实现高度的灵活性。
- 获取类型信息:包含类名、命名空间、继承层次结构等。
- 成员访问:访问和操作字段、属性、方法、事件等。
动态对象创建和方法调用
反射最直观的用途是动态的创建对象和调用方法。这使得开发者可以在不知道对象确切类型的情况下,进行对象的实例化和方法调用。
- 动态对象创建:通过类型名称动态穿件对象实例。
- 动态方法执行:在运行时调用方法,包括公有、私有方法和重载方法。
动态代理和拦截
反射可以用来实现动态代理和方法拦截,这在很多高级编程场景中非常有用,比如实现AOP(面向切面编程)
- 动态代理:创建一个对象代理,代理对象可以在目标对象的方法调用前后执行额外的逻辑。
- 方法拦截:拦截对特定方法的调用,可以用于日志记录、性能监测、事务处理等。
using System; using System.Reflection; // 定义接口 public interface ICalculator { int Add(int a, int b); } // 实际类 public class Calculator : ICalculator { public int Add(int a, int b) { return a + b; } } // 代理类 public class CalculatorProxy : ICalculator { private readonly Calculator _calculator; public CalculatorProxy() { _calculator = new Calculator(); } public int Add(int a, int b) { // 在调用实际对象方法前执行一些逻辑 Console.WriteLine("Before calling Add method"); // 使用反射调用实际对象的方法 MethodInfo methodInfo = typeof(Calculator).GetMethod("Add"); int result = (int)methodInfo.Invoke(_calculator, new object[] { a, b }); // 在调用实际对象方法后执行一些逻辑 Console.WriteLine("After calling Add method"); return result; } } class Program { static void Main() { // 使用代理对象调用方法 ICalculator calculator = new CalculatorProxy(); int sum = calculator.Add(3, 5); Console.WriteLine("Sum: " + sum); } }
在上面的示例中,
CalculatorProxy
类实现了ICalculator
接口,并使用反射在调用实际对象的方法前后执行了附加的逻辑。然后,我们使用代理对象CalculatorProxy
而不是直接调用实际对象Calculator
,以实现对方法调用的拦截和控制。
自定义属性(Attribute)处理
反射允许程序检查代码中的自定义属性,这是实现各种框架(如测试框架、ORM框架等)的基础。
- 属性读取:读取类、方法、字段等上的自定义属性,用于配置或特殊处理。
- 属性驱动的逻辑:基于自定义属性执行特定逻辑,如序列化/反序列化、数据库操作等。
using System; using System.Reflection; // 定义一个自定义属性 [AttributeUsage(AttributeTargets.Class)] public class MyAttribute : Attribute { public string Description { get; } public MyAttribute(string description) { Description = description; } } // 使用自定义属性 [My("This is a custom attribute")] class MyClass { // 类的成员和方法 } class Program { static void Main(string[] args) { // 获取 MyClass 类的类型对象 Type type = typeof(MyClass); // 获取 MyClass 类上的所有自定义属性 object[] attributes = type.GetCustomAttributes(typeof(MyAttribute), false); // 遍历自定义属性并输出其描述信息 foreach (var attribute in attributes) { if (attribute is MyAttribute myAttribute) { Console.WriteLine("Description: " + myAttribute.Description); } } } }
在上面的示例中,我们定义了一个名为
MyAttribute
的自定义属性,并将其应用于MyClass
类。然后,我们使用反射获取MyClass
类的类型对象,并使用GetCustomAttributes
方法获取类上的所有自定义属性。最后,我们遍历自定义属性数组,并输出每个属性的描述信息。
反射的性能
反射是相当大的机制,允许在运行时发现并使用编译时还不了解的类型及成员。但是他也有下面两个缺点。
- 反射造成编译时无法保证类型安全性。由于反射严重依赖字符串,所以会丧失编译时
的类型安全性。例如,执行Type.GetType("nt");要求通过反射在程序集中查找名为
"int"的类型,代码会通过编译,但在运行时会返回null,因为CLR只知"System.Int32",
不知"int'"。 - 反射速度慢。使用反射时,类型及其成员的名称在编译时未知;你要用字符串名称标
识每个类型及其成员,然后在运行时发现它们。也就是说,使用System.Reflection命
名空间中的类型扫描程序集的元数据时,反射机制会不停地执行字符串搜索。通常,
字符串搜索执行的是不区分大小写的比较,这会进一 步影响速度。
使用反射 调用成员也会影响性能。用反射调用方法时,首先必须将实参打包(pack)成数组;在内部,反射必须将这些实参解包(unpack)到线程栈上,此外,在调用方法前,CLR必须检查实参具有正确的数据类型。最后,CLR必须确保调动这正确的安全权限来访问被调用的成员。
基于上述原因,最好避免利用反射来范文字段或调用方法/属性。应该利用一下两种技术开发应用程序来动态发现和构造类型实例。
发现程序集中定义的类型
反射经常用于判断程序集定义了哪些类型。FCL提供了许多API来获取这方面的信息。目前最常用的API是Assembly的ExportedTypes属性。示例如下:
using System.Reflection;
String dataAssembly = "System.Data, version=4.0.0.0, culture= neutral, PublicKeyToken=b77a5c561934e089";
LoadAssemAndShowPublicTypes(dataAssembly);
static void LoadAssemAndShowPublicTypes(string assemid)
{
//显示的将程序集加载到AppDoain中
var assembly = Assembly.Load(assemid);
//在一个循环汇总显示已加载程序集中每个开导出Type的全名
foreach (var type in assembly.ExportedTypes)
{
//显示全名
Console.WriteLine(type.FullName);
}
}
构造类型的实例
获得对Type派生对象的引用后,就可以构造该类型的实例了。FCL提供了以下几个机制。
- System.Activator的CreateInstance方法
Activator类提供了静态CreateInstance方法的几个重载版本。调用方法时既可传递一
个Type对象引用,也可传递标识了类型的String。直接获取类型对象的几个版本较为
简单。你要为类型的构造器传递一组实参,方法返回对新对象的引用。
用字符串来指定类型的几个版本则稍微复杂一些。首先必须指定另一个字符串来标识
定义了类型的程序集。其次,如果正确配置了远程访问(remoting)选项,这些方法还允
许构造远程对象。第三,这些版本返回的不是对新对象的引用,而是一个
System.Runtime.Remoting.ObjectHandle对象(从System.MarshalByRefObject派生)。
ObjectHandle类型允许将一个 AppDomain中创建的对象传至其他AppDomain,期间
不强迫对象具体化(materialize)。准备好具体化这个对象时,请调用ObjectHandle 的
Unwrap方法。在一个AppDomain中调用该方法时,它将定义了要具体化的类型的程
序集加载到这个AppDomain中。如果对象按引用封送,会创建代理类型和对象。如果
对象按值封送,对象的副本会被反序列化。
using System;
public class Program
{
public static void Main(string[] args)
{
// 要创建实例的类必须具有一个公共的无参数构造函数
// 这里我们创建了一个名为 Person 的类,它有一个无参数的构造函数
// 你可以替换为你自己的类
Type type = typeof(Person);
// 使用 Activator.CreateInstance 创建 Person 类的实例
object instance = Activator.CreateInstance(type);
// 将 object 强制转换为 Person 类型
Person person = (Person)instance;
// 现在你可以使用该实例进行操作
person.Name = "John";
person.Age = 30;
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// 无参数的构造函数
public Person()
{
// 这里可以初始化属性或执行其他操作
}
}
- System.Activator的CreatelnstanceFrom方法
Activator类还提供了一组静 态CreateInstanceFrom方法。它们与CreateInstance的
行为相似,只是必须通过字符串参数来指定类型及其程序集。程序集用Assembly 的
LoadFrom(而非Load)方法加载到调用AppDomain中。由于都不接受Type参数,所
以返回的都是一个ObjectHandle对象引用,必须调用ObjectHandle的Unwrap方法
进行具体化。
using System;
public class Program
{
public static void Main(string[] args)
{
// 指定包含要创建类型的程序集的路径
string assemblyPath = @"C:\Path\To\Your\Assembly.dll";
// 指定要创建的类型的全名(包括命名空间)
string typeName = "YourNamespace.YourClass";
// 使用 Activator.CreateInstanceFrom 创建指定程序集中的指定类型的实例
object instance = Activator.CreateInstanceFrom(assemblyPath, typeName).Unwrap();
// 将 object 强制转换为指定类型
YourNamespace.YourClass obj = (YourNamespace.YourClass)instance;
// 现在你可以使用该实例进行操作
obj.SomeMethod();
}
}
- System.AppDomain的方法
AppDomain类型提供了4个用于构造类型实例的实例方法(每个都有几个重载版.
本),包括CreateInstance ,CreateInstanceAndUnwrap, CreateInstanceFrom 和
CreatelnstanceFromAndUnwrap。这些方法的行为和Activator 类的方法相似,区别
在于它们都是实例方法,允许指定在哪个AppDomain中构造对象。另外,带Unwrap
后缀的方法还能简化操作,不必执行额外的方法调用。
using System;
class Program
{
static void Main()
{
// 创建一个新的应用程序域
AppDomain domain = AppDomain.CreateDomain("MyAppDomain");
// 在新的应用程序域中创建指定类型的实例
object instance = domain.CreateInstance("AssemblyName", "TypeName");
// 将 object 强制转换为指定类型
YourType obj = (YourType)instance;
// 现在你可以使用该实例进行操作
obj.SomeMethod();
// 卸载应用程序域
AppDomain.Unload(domain);
}
}
- System.Reflection.Constructorlnfo的Invoke实例方法
使用一个Type对象引用,可以绑定到一个特定的构造器,并获取对构造器的
ConstructorInfo对象的引用。然后,可利用ConstructorInfo 对象引用来调用它的
Invoke方法。类型总是在调用AppDomain中创建,返回的是对新对象的引用。
using System;
using System.Reflection;
class Program
{
static void Main()
{
// 获取要调用的构造函数信息对象
ConstructorInfo constructorInfo = typeof(YourType).GetConstructor(Type.EmptyTypes);
// 使用构造函数信息对象调用构造函数实例化对象
object instance = constructorInfo.Invoke(null);
// 将 object 强制转换为指定类型
YourType obj = (YourType)instance;
// 现在你可以使用该实例进行操作
obj.SomeMethod();
}
}
反射示例
包含私有字段、属性、事件、方法的调用
using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Reflection;
using System.Text;
var t = typeof(SomeType);
BindToMemberThenInvokeTheMember(t);
Console.WriteLine();
BindToMemberCreateDe1egateToMemberThenInvokeTheMember(t);
Console.WriteLine();
UseDynamicToBindAndInvokeTheMember(t);
Console.WriteLine();
void BindToMemberThenInvokeTheMember(Type t)
{
Console.WriteLine("BindToMemberThenInvokeTheMember");
//构造实例
Type ctorArgument = Type.GetType("System.Int32"); //或者typeof(int32).MakeByRefType();
ConstructorInfo ctor = t.GetTypeInfo().DeclaredConstructors.First(c => c.GetParameters()[0].ParameterType == ctorArgument);
object[] args = new Object[] { 12 };//构造器的实参
Console.WriteLine("x before constructor called:" + args[0]);
object obj = ctor.Invoke(args);
Console.WriteLine($"type: {obj.GetType()}");
Console.WriteLine($"x after constructor returns: {args[0]}");
//读取字段
FieldInfo fi = obj.GetType().GetTypeInfo().GetDeclaredField("m_someField");//或 t.GetField(m_someField);
//var fi1=t.GetField("m_someField");
fi.SetValue(obj, 33);
Console.WriteLine($"x_someField : {fi.GetValue(obj)}");
//调用方法
MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString");
string s = (String)mi.Invoke(obj, null);
Console.WriteLine($"Tostring : {s}");
//读写属性
PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp");
try
{
pi.SetValue(obj, 0, null);
}
catch (TargetInvocationException e)
{
if (e.InnerException.GetType() != typeof(ArgumentOutOfRangeException)) throw;
Console.WriteLine("Property set catch.");
pi.SetValue(obj, 2, null);
Console.WriteLine($"SomeProp: {pi.GetValue(obj, null)}");
}
//为事件添加和删除委托
EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent");
EventHandler eh = new EventHandler(EventCallback);
ei.AddEventHandler(obj, eh);
ei.RemoveEventHandler(obj, eh);
}
void EventCallback(object sender, EventArgs e) { }
void BindToMemberCreateDe1egateToMemberThenInvokeTheMember(Type t)
{
Console.WriteLine("BindToMemberCreateDe legateToMemberThenI nvokeTheMember");
//构造实例(不能创建对构造器的委托)
object[] args = new object[] { 12 }; //构造器实参
Console.WriteLine("x before constructor called:" + args[0]);
object obj = Activator.CreateInstance(t, args);
Console.WriteLine("Type:" + obj.GetType().ToString());
Console.WriteLine("x after constructor returns: " + args[0]);
//注意:不能创建对字段的委托
//调用方法
MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString");
var toString = mi.CreateDelegate<Func<string>>(obj);
String s = toString();
Console.WriteLine("ToString:" + s);
//读写属性
PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp");
var setSomeProp = pi.SetMethod.CreateDelegate<Action<Int32>>(obj);
try
{
setSomeProp(0);
}
catch (ArgumentOutOfRangeException)
{
Console.WriteLine("Property set catch.");
}
setSomeProp(2);
}
void UseDynamicToBindAndInvokeTheMember(Type t)
{
Console.WriteLine("UseDynami CToBi ndAndInvokeTheMember");
//构造实例(不能创建对构造器的委托)
object[] args = new object[] { 12 }; // 1 构造器的实参
Console.WriteLine("x before constructor called: " + args[0]);
dynamic obj = Activator.CreateInstance(t, args);
Console.WriteLine("Type:" + obj.GetType().ToString());
Console.WriteLine("x after constructor returns: " + args[0]);
//读写字段
try
{
obj.m_someField = 5;
Int32 v = (Int32)obj.m_someField;
Console.WriteLine("someField:" + v);
}
catch (RuntimeBinderException e)
{
//之所以会执行到这里,是因为字段是私有的
Console.WriteLine("Failed to access field: " + e.Message);
}
//调用方法
String s = (String)obj.ToString();
Console.WriteLine("ToString:" + s);
//读写属性
try
{
obj.SomeProp = 0;
}
catch (ArgumentOutOfRangeException)
{
Console.WriteLine("Property set catch.");
}
obj.SomeProp = 2;
Int32 val = (Int32)obj.SomeProp;
Console.WriteLine("SomeProp:" + val);
// 从事件增删委托
obj.SomeEvent += new EventHandler(EventCallback);
obj.SomeEvent -= new EventHandler(EventCallback);
}
internal static class ReflectionExtensions
{
//这个辅助扩展方法简化了创建委托的语法
public static TDelegate CreateDelegate<TDelegate>(this MethodInfo mi, object target = null)
{
return (TDelegate)(object)mi.CreateDelegate(typeof(TDelegate), target);
}
}
//该类用于演示反射机制。
//其中定义了一个字段、构造器、方法、属性和一个事件
internal sealed class SomeType
{
private Int32 m_someField;
public SomeType(Int32 x)
{
x *= 2;
}
public override string ToString()
{
return m_someField.ToString();
}
public Int32 SomeProp
{
get { return m_someField; }
set
{
if (value < 1)
throw new ArgumentOutOfRangeException("value");
m_someField = value;
}
}
public event EventHandler SomeEvent;
private void NoCompilerWarnings()
{
SomeEvent.ToString();
}
}