4

私がやろうとしているのは、横断的関心事のためにオブジェクトのメソッドとプロパティへの呼び出しをインターセプトできるようにすることです。を使用してプロキシベースのAOPを使用してContextBoundObjectいます。

ただし、これは再帰的なメソッド呼び出しでは機能しません。ターゲットに対する最初の呼び出しはプロキシによってインターセプトされて正常に呼び出されるため、ここでクロスカットを実行できます。ただし、最初のメソッド内からの後続のメソッド呼び出しはターゲットクラス内にとどまり、マーシャリングが発生しないかのようにプロキシによってインターセプトされません。

それを機能させる方法はありますか?(PostSharp、Unity、Spring.Netなどのサードパーティライブラリを避けようとしています)

class Program
{
    static void Main(string[] args)
    {
        var t = new SimpleObject();
        t.TestMethod1();
    }
}


[Intercept]
class SimpleObject : ContextBoundObject
{
    public string TestMethod1()
    {
        return TestMethod2();
    }

    public string TestMethod2()
    {
        return "test";
    }
}

[AttributeUsage(AttributeTargets.Class)]
public class InterceptAttribute : ContextAttribute, IContributeObjectSink
{
    public InterceptAttribute()
        : base("Intercept")
    { }

    public override bool IsContextOK(Context ctx, IConstructionCallMessage ctorMsg)
    {
        return false;
    }

    public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink nextSink)
    {
        return new InterceptSink(nextSink);
    }
}


public class InterceptSink : IMessageSink
{
    public IMessageSink NextSink { get; private set; }

    public InterceptSink(IMessageSink nextSink)
    {
        this.NextSink = nextSink;
    }

    public IMessage SyncProcessMessage(IMessage msg)
    {
        IMethodCallMessage mcm = (msg as IMethodCallMessage);

        // { cross-cut here }

        IMessage rtnMsg = this.NextSink.SyncProcessMessage(msg);
        IMethodReturnMessage mrm = (rtnMsg as IMethodReturnMessage);

        // { cross-cut here }

        return mrm;
    }

    public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
    {
        return null;
    }
}
4

1 に答える 1

4

C# の設計者が AOP を支持したことはありません。プロキシとマーシャリングを使用せずにメソッド呼び出しをインターセプトする簡単な方法はありません。プロキシとマーシャリングにはそれぞれ欠点があります。誰かがメソッド/プロパティの呼び出しを傍受したい場合 (たとえば、分野横断的な関心事) にRealProxy役立つことがわかりました。

MSDN からの RealProxy:

あらゆる種類のリモーティング境界を越えてオブジェクトを使用するクライアントは、実際にはオブジェクトに透過プロキシを使用しています。透過プロキシは、実際のオブジェクトがクライアントの空間に存在するという錯覚をもたらします。これは、リモーティング インフラストラクチャを使用して、そのオブジェクトに対して行われた呼び出しを実際のオブジェクトに転送することによって実現されます。

注: を使用してプロキシされる型は、インターフェイスまたは継承元RealProxyのいずれかである必要があります。MarshalByRefObject

RealProxyファクトリ メソッドを使用して実行時にオブジェクトのプロキシを作成する実装を次に示します。

public abstract class RuntimeProxy
{
    public static readonly object Default = new object();

    public static Target Create<Target>(Target instance, RuntimeProxyInterceptor interceptor) where Target : class
    {
        return (Target)new InternalProxy<Target>(instance, interceptor).GetTransparentProxy();
    }

    public static Target Create<Target>(Target instance, Func<RuntimeProxyInvoker, object> factory) where Target : class
    {
        return (Target)new InternalProxy<Target>(instance, new InternalRuntimeProxyInterceptor(factory)).GetTransparentProxy();
    }


    class InternalProxy<Target> : RealProxy where Target : class
    {
        readonly object Instance;
        readonly RuntimeProxyInterceptor Interceptor;

        public InternalProxy(Target instance, RuntimeProxyInterceptor interceptor)
            : base(typeof(Target))
        {
            Instance = instance;
            Interceptor = interceptor;
        }

        public override IMessage Invoke(IMessage msg)
        {
            var methodCall = (IMethodCallMessage)msg;
            var method = (MethodInfo)methodCall.MethodBase;

            try
            {
                var result = Interceptor.Invoke(new InternalRuntimeProxyInterceptorInvoker(Instance, method, methodCall.InArgs));

                if (result == RuntimeProxy.Default)
                    result = method.ReturnType.IsPrimitive ? Activator.CreateInstance(method.ReturnType) : null;

                return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
            }
            catch (Exception ex)
            {
                if (ex is TargetInvocationException && ex.InnerException != null)
                    return new ReturnMessage(ex.InnerException, msg as IMethodCallMessage);

                return new ReturnMessage(ex, msg as IMethodCallMessage);
            }
        }
    }

    class InternalRuntimeProxyInterceptor : RuntimeProxyInterceptor
    {
        readonly Func<RuntimeProxyInvoker, object> Factory;

        public InternalRuntimeProxyInterceptor(Func<RuntimeProxyInvoker, object> factory)
        {
            this.Factory = factory;
        }

        public override object Invoke(RuntimeProxyInvoker invoker)
        {
            return Factory(invoker);
        }
    }

    class InternalRuntimeProxyInterceptorInvoker : RuntimeProxyInvoker
    {
        public InternalRuntimeProxyInterceptorInvoker(object target, MethodInfo method, object[] args)
            : base(target, method, args)
        { }
    }
}

public abstract class RuntimeProxyInterceptor
{
    public virtual object Invoke(RuntimeProxyInvoker invoker)
    {
        return invoker.Invoke();
    }
}

public abstract class RuntimeProxyInvoker
{
    public readonly object Target;
    public readonly MethodInfo Method;
    public readonly ReadOnlyCollection<object> Arguments;

    public RuntimeProxyInvoker(object target, MethodInfo method, object[] args)
    {
        this.Target = target;
        this.Method = method;
        this.Arguments = new ReadOnlyCollection<object>(args);
    }

    public object Invoke()
    {
        return Invoke(this.Target);
    }

    public object Invoke(object target)
    {
        if (target == null)
            throw new ArgumentNullException("target");

        try
        {
            return this.Method.Invoke(target, this.Arguments.ToArray());
        }
        catch (TargetInvocationException ex)
        {
            throw ex.InnerException;
        }
    }
}

をファクトリとして使用しRuntimeProxyて、オブジェクトのプロキシを作成し、すべてのメソッド/プロパティ呼び出しをインターセプトして、実際のメソッドを呼び出すことができます。

サンプルは次のとおりです。

class SomeClass : MarshalByRefObject
{
    public int Mul(int a, int b)
    {
        return a * b;
    }

    public void SetValue(int val)
    {
        this.Val = val;
    }

    public int Val { get; set; }
}

class を使用RuntimeProxyして、クラスのインスタンスのプロキシを作成しSomeClass、呼び出しをインターセプトします。

var test = new SomeClass();
var proxy = RuntimeProxy.Create(test, t =>
{
    // cross-cut here

    return t.Invoke();          // invoke the actual call
});

var res = proxy.Mul(3, 4);      // method with return value
proxy.SetValue(2);              // void method, setting some property
var val = proxy.Val;            // property access

MarshalByRefObjectクラスから継承したくない場合は、インターフェイス型を使用できます。

于 2015-08-14T18:09:49.463 に答える