C# 实现AOP(面向切面编程),以拦截属性setter/getter或方法调用(原生实现)

目录

Ⅰ 为什么需要AOP

Ⅱ AOP常见应用场景

1.安全/日志

2.游戏外挂

Ⅲ C#实现

1.实现一个代理中间件

2.实现一个扩展方法,用于为对象生成代理中间件

3.将class中需要拦截的属性和方法抽象为接口

(1).假定已有如下class

(2).IPropertyProxy接口实现

4.为对象实例创建代理,实现AOP

Ⅳ 作者在一个WPF类库项目中实现的AOP

MinimalisticWPF ( .NET6.0 / .NET8.0 )

(1).拦截示例

(2).获取库

Ⅴ 总结


Ⅰ 为什么需要AOP

AOP可视作对OOP(面向对象编程)的补充,OOP将事物抽象为class,并赋予对象交互行为,这么做的确便于处理复杂业务逻辑,但当业务需求更改,就需要修改class,然而问题正出现在这里,一旦class涉及到继承与多态,修改行为可能会破坏一整条逻辑链……于是,AOP诞生了,它做的事情正如同它的名字那样,在每个功能触发的那个时刻打开一道缺口,也就是“切面”,接着,你可在这个“切面”的前后添加自定义的逻辑,当然,你还可以直接覆盖默认的功能实现,此时,我们扩展/修改了功能,但我们并不需要修改class源码

Ⅱ AOP常见应用场景

1.安全/日志

例如你是一个小白,但你的业务逻辑中涉及私密数据的读写,那么老师傅会拦截你的数据处理逻辑,在前后添加安全验证、日志记录等逻辑,这个过程不用修改class,,这种场景C#是提供了原生实现的,也是本文着重介绍的。

2.游戏外挂

例如游戏里有个伤害方法Hit,你同样可以做到拦截Hit调用,并覆写为新的逻辑,虽然这需要一些额外的手段,比如你需要反编译以理解原始逻辑,比如你需要一些IL注入修改工具……

Ⅲ C#实现

这里使用WPF(.NET6.0)作为演示

1.实现一个代理中间件

【Invoke】拦截了方法调用,在这个函数中可以转发方法调用给到原始对象并扩展/覆写方法调用

    public class ProxyInstance : DispatchProxy
    {
        //公开无参构造函数是必需的
        public ProxyInstance() { }

        //被代理的对象实例
        internal object? _target;
    
        //此处会拦截方法调用,参数一是方法信息,参数二是本次传给方法的实参
        protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
        {
            //先获取被调用的方法名
            var Name = targetMethod?.Name ?? string.Empty;
            if (Name == string.Empty) return null;

            //以get_开头的方法名是C#属性getter器
            //以set_开头的方法名是C#属性setter器
            //其它方法名是定义的函数的名字
            if (Name.StartsWith("get_"))
            {
                //…………此处可在get前执行自定义逻辑

                //默认行为,可覆写为自定义逻辑
                var result = _targetType?.GetMethod(Name)?.Invoke(_target, args);

                //…………此处可在get后执行自定义逻辑                

                return result;
            }
            else if (Name.StartsWith("set_"))
            {
                var result = _targetType?.GetMethod(Name)?.Invoke(_target, args);
                return result;
            }
            else
            {
                var result = _targetType?.GetMethod(Name)?.Invoke(_target, args);
                return result;
            }
        }

2.实现一个扩展方法,用于为对象生成代理中间件

【IProxy】是一个自定义的基础接口,因为C#原生实现只能对接口创建代理,所以这里我自己给了一个接口限制,防止传入奇怪的对象实例

        public static T CreateProxy<T>(this T target) where T : IProxy
        {
            dynamic proxy = DispatchProxy.Create<T, ProxyInstance>() ?? throw new InvalidOperationException();
            proxy._target = target;
            return proxy;
        }

3.将class中需要拦截的属性和方法抽象为接口

(1).假定已有如下class

我们想拦截Name的get和set行为,就需要将这个属性抽象到接口中,方法亦是如此

public class TObj : IPropertyProxy
{
    public TObj() { }

    public string Name { get; set; } = "defaultValue";

    public string GetName()
    {
        return "defaultResult";
    }
}

(2).IPropertyProxy接口实现

这里相当于声明了我们想要拦截Name属性和GetName方法

public interface IPropertyProxy : IProxy
{
    string Name { get; set; }
    string GetName();
}

4.为对象实例创建代理,实现AOP

TObj obj = new TObj();
IPropertyProxy pro = obj.CreateProxy<IPropertyProxy>();

此时,我们调取 [ pro.Name ] 时,会在先前的 [ Invoke ] 处被拦截 , 而 [ obj.Name ] 就不会被拦截 , 并且如果我们并没有覆写原始逻辑 , 那么 [ pro.Name = "newValue" ] 执行后,[ obj.Name ] 的值也会变成新的值,这就是 [ 代理 ] 的效果 。

Ⅳ 作者在一个WPF类库项目中实现的AOP

MinimalisticWPF ( .NET6.0 / .NET8.0 )

(1).拦截示例

这里依旧是上面定义的TObj类,类库提供了简单的拦截构建语法,于是代理对象pro可以拦截GetName方法调取并转用自定义的逻辑

TObj obj = new TObj();
IPropertyProxy pro = obj.CreateProxy<IPropertyProxy>();
pro.SetMethod(nameof(pro.GetName), 
            object? (args) => { MessageBox.Show($"before method call"); return null; }, 
            object? (args) => { return "coverage result"; }, 
            object? (args) => { MessageBox.Show($"after method call"); return null; });

(2).获取库

github

nuget

Ⅴ 总结

(1) C#实现AOP的三个重要环节:

1.对需要代理的属性/方法做接口抽象

2.对 Invoke 做细节处理

3.创建代理对象

(2) 注意点:

1.警惕stackoverflow隐患:

例如你覆写的getter逻辑中存在对代理对象的getter调用,那么当你通过代理对象触发getter时会直接stackoverflow

2.性能:

基于方法调用都会经过代理转发给原始对象,因此它通常不是高性能的,对于高频次的调取,需要在原始对象和代理对象间权衡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值