You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过反射重写属性调用自定义方法?是否必须用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:52:33