如何通过反射重写属性调用自定义方法?是否必须用PropertyBuilder?
如何通过反射重写已有类的属性访问逻辑?
首先得明确一点:咱们没法直接修改已经加载到CLR里的现有类的属性实现。.NET的类型系统是静态的,一旦类型被加载到AppDomain中,它的定义就固定死了,不能再修改原有属性的get/set逻辑。所以你提到的PropertyBuilder+ILGenerator确实是一种可行方案,但不是唯一的,还有更省心的替代选项。
先看你的示例代码:
原类定义:
public class Foo : MyContext { public int Bar { get; set; } public int Baz { get; set; } } public class MyContext { public MyContext() { var props = this.GetType().GetProperties(); foreach(var p in props) { //... } } protected void CustomSet(string propName, int value) { /*...*/ } protected int CustomGet(string propName) { /*...*/ } }
你期望实现的效果是生成类似这样的子类:
public class FooReflected : MyContext { public int Bar { get { return CustomGet("Bar"); } set { CustomSet("Bar", value); } } public int Baz { get { return CustomGet("Baz"); } set { CustomSet("Baz", value); } } }
下面分几种方式给你说明:
1. 用PropertyBuilder+ILGenerator动态生成子类
这是最底层的实现方式,需要手动编写IL指令来生成属性的get/set方法,直接调用基类的CustomGet和CustomSet。示例代码如下:
using System.Reflection; using System.Reflection.Emit; public static Type GenerateCustomizedSubclass(Type baseType) { // 定义动态程序集和模块 var assemblyName = new AssemblyName($"Dynamic_{baseType.Name}_Assembly"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule($"{baseType.Name}_Module"); // 创建继承自baseType的动态类 var typeBuilder = moduleBuilder.DefineType( $"{baseType.Name}Reflected", TypeAttributes.Public | TypeAttributes.Class, baseType); // 获取基类的自定义方法(注意BindingFlags要包含非公共实例方法) var customGetMethod = baseType.GetMethod( "CustomGet", BindingFlags.NonPublic | BindingFlags.Instance); var customSetMethod = baseType.GetMethod( "CustomSet", BindingFlags.NonPublic | BindingFlags.Instance); // 遍历基类的所有int类型公共属性 foreach (var prop in baseType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (prop.PropertyType != typeof(int)) continue; // 生成属性的get方法 var getMethod = typeBuilder.DefineMethod( $"get_{prop.Name}", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(int), Type.EmptyTypes); var getIl = getMethod.GetILGenerator(); // 加载this指针 -> 加载属性名字符串 -> 调用CustomGet -> 返回结果 getIl.Emit(OpCodes.Ldarg_0); getIl.Emit(OpCodes.Ldstr, prop.Name); getIl.Emit(OpCodes.Call, customGetMethod); getIl.Emit(OpCodes.Ret); // 生成属性的set方法 var setMethod = typeBuilder.DefineMethod( $"set_{prop.Name}", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(void), new[] { typeof(int) }); var setIl = setMethod.GetILGenerator(); // 加载this指针 -> 加载属性名字符串 -> 加载传入的value参数 -> 调用CustomSet -> 返回 setIl.Emit(OpCodes.Ldarg_0); setIl.Emit(OpCodes.Ldstr, prop.Name); setIl.Emit(OpCodes.Ldarg_1); setIl.Emit(OpCodes.Call, customSetMethod); setIl.Emit(OpCodes.Ret); // 将get/set方法绑定到属性上 var propBuilder = typeBuilder.DefineProperty( prop.Name, PropertyAttributes.None, typeof(int), null); propBuilder.SetGetMethod(getMethod); propBuilder.SetSetMethod(setMethod); } // 创建并返回动态类型 return typeBuilder.CreateType(); } // 使用方式 var dynamicType = GenerateCustomizedSubclass(typeof(Foo)); var instance = Activator.CreateInstance(dynamicType); // 此时访问instance的Bar/Baz属性,就会调用MyContext里的CustomGet/CustomSet
2. 用Castle DynamicProxy简化实现(不用写IL)
如果不想手动写IL,可以用Castle DynamicProxy这类第三方库,它帮你封装了动态代理的逻辑,只需要写拦截器即可:
using Castle.DynamicProxy; public class PropertyAccessInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { // 判断是否是属性的get/set方法 if (invocation.Method.IsSpecialName && (invocation.Method.Name.StartsWith("get_") || invocation.Method.Name.StartsWith("set_"))) { var propName = invocation.Method.Name.Substring(4); // 去掉get_或set_前缀 var context = invocation.InvocationTarget as MyContext; if (context == null) return; if (invocation.Method.Name.StartsWith("get_")) { // 拦截get方法,返回CustomGet的结果 invocation.ReturnValue = context.CustomGet(propName); return; } else if (invocation.Method.Name.StartsWith("set_")) { // 拦截set方法,调用CustomSet if (invocation.Arguments[0] is int value) { context.CustomSet(propName, value); } invocation.ReturnValue = null; return; } } // 非属性方法直接执行原逻辑 invocation.Proceed(); } } // 使用方式 var proxyGenerator = new ProxyGenerator(); var interceptor = new PropertyAccessInterceptor(); var fooProxy = proxyGenerator.CreateClassProxy<Foo>(interceptor); // 访问fooProxy的Bar/Baz属性时,会被拦截并调用CustomGet/CustomSet
3. 用Roslyn动态生成代码并编译
另一种思路是用Roslyn直接生成你期望的类的代码字符串,然后动态编译成程序集并加载。这种方式更直观,因为你可以直接写C#代码而不是IL:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using System.Reflection; public static Type GenerateClassWithRoslyn(Type baseType) { // 生成类的代码字符串 var propsCode = string.Join("\n", baseType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType == typeof(int)) .Select(p => $@" public int {p.Name} {{ get {{ return CustomGet(""{p.Name}""); }} set {{ CustomSet(""{p.Name}"", value); }} }}")); var code = $@" public class {baseType.Name}Reflected : {baseType.FullName} {{ {propsCode} }}"; // 编译代码 var syntaxTree = CSharpSyntaxTree.ParseText(code); var references = AppDomain.CurrentDomain.GetAssemblies() .Where(a => !a.IsDynamic) .Select(a => MetadataReference.CreateFromFile(a.Location)) .ToList(); var compilation = CSharpCompilation.Create( $"Dynamic_{baseType.Name}_Assembly", new[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using var ms = new MemoryStream(); var result = compilation.Emit(ms); if (!result.Success) { throw new Exception("编译失败:" + string.Join("\n", result.Diagnostics.Select(d => d.GetMessage()))); } ms.Seek(0, SeekOrigin.Begin); var assembly = Assembly.Load(ms.ToArray()); return assembly.GetType($"{baseType.Name}Reflected"); } // 使用方式 var roslynType = GenerateClassWithRoslyn(typeof(Foo)); var instance = Activator.CreateInstance(roslynType);
总结一下
- 不能直接修改现有类的属性逻辑,必须通过生成新类型(动态子类、代理类)来实现;
PropertyBuilder+ILGenerator是最底层的方式,灵活但需要掌握IL指令;- Castle DynamicProxy、Roslyn这类工具可以大大简化开发,不用直接操作IL,更易维护。
内容的提问来源于stack exchange,提问作者Max Bündchen
相关产品推荐
相关产品推荐

