代码之家  ›  专栏  ›  技术社区  ›  mr.b Scott Lystig Fritchie

如何代理对对象实例的调用

  •  3
  • mr.b Scott Lystig Fritchie  · 技术社区  · 14 年前

    编辑: 将问题标题改为“C是否允许方法重载,PHP风格(#调用)”—发现它与实际问题没有太大关系。还编辑了问题文本。

    我要完成的是代理对对象方法实例的调用,这样我就可以记录对其任何方法的调用。

    现在,我有类似的代码:

    class ProxyClass {
        static logger;
        public AnotherClass inner { get; private set; }
        public ProxyClass() { inner = new AnotherClass(); }
    }
    
    class AnotherClass {
        public void A() {}
        public void B() {}
        public void C() {}
        // ...
    }
    
    // meanwhile, in happyCodeLandia...
    ProxyClass pc = new ProxyClass();
    pc.inner.A(); // need to write log message like "method A called"
    pc.inner.B(); // need to write log message like "method B called"
    // ...
    

    那么,如何以可扩展的方式代理对对象实例的调用呢?方法重载将是最明显的解决方案(如果PHP支持的话)。通过可扩展,这意味着我不必在其他类更改时修改ProxyClass。

    在我的例子中,另一个类可以有任意数量的方法,因此不适合重载或包装所有方法来添加日志记录。

    我知道这可能不是解决这类问题的最佳方法,所以如果有人知道该使用什么方法,那就开枪吧。

    谢谢!

    5 回复  |  直到 14 年前
        1
  •  2
  •   Community Dunja Lalic    7 年前

    相互呼应;DI是一条路要走。动态代理在这方面非常胜任。

    下面是一些示例代码,包括所有必需的实现。通常,根据接口编写代码是一种很好的做法。

    Help and Information about Aspect Oriented Programming

    (编辑:因为你很好,给了我一些分数,这里有另一个很酷的链接到DP教程,它做得很好: http://kozmic.pl/archive/2009/04/27/castle-dynamic-proxy-tutorial.aspx ;))

    下面是示例代码:

    using System;
    using System.Collections.Generic;
    using Castle.Core;
    using Castle.Core.Interceptor;
    using Castle.MicroKernel.Registration;
    using Castle.Windsor;
    using NUnit.Framework;
    
    [TestFixture]
    public class LoggingMethodInvocationsTests
    {
        [Test]
        public void CanLogInvocations()
        {
            var container = new WindsorContainer();
            container.Register(Component.For<LoggingInterceptor>().LifeStyle.Singleton);
            // log all calls to the interface
            container.Register(Component.For<IA>().ImplementedBy<A>().Interceptors(typeof (LoggingInterceptor)));
    
            var a = container.Resolve<IA>();
            a.AMethod(3.1415926535); // to interface
            Console.WriteLine("End of test");
        }
    }
    
    public class LoggingInterceptor : IInterceptor, IOnBehalfAware
    {
        private string _entityName;
    
        public void Intercept(IInvocation invocation)
        {
            var largs = new List<string>(invocation.Arguments.Length);
    
            for (int i = 0; i < invocation.Arguments.Length; i++)
                largs.Add(invocation.Arguments[i].ToString());
    
            var a = largs.Count == 0 ? "[no arguments]" : string.Join(", ", largs.ToArray());
            var method = invocation.Method == null ? "[on interface target]" : invocation.Method.Name;
    
            Console.WriteLine(string.Format("{0}.{1} called with arguments {2}", _entityName, method, a));
    
            invocation.Proceed();
    
            Console.WriteLine(string.Format("After invocation. Return value {0}", invocation.ReturnValue));
        }
    
        public void SetInterceptedComponentModel(ComponentModel target)
        {
            if (target != null)
                _entityName = target.Implementation.FullName;
        }
    }
    
    public class A : IA
    {
        public double AMethod(double a)
        {
            Console.WriteLine("A method impl");
            return a*2;
        }
    
        public void SecondMethod(double a)
        {
            Console.WriteLine(string.Format("Impl: SecondMethod called with {0}", a));
        }
    }
    
    public interface IA
    {
        double AMethod(double a);
    }
    

    控制台输出

    Examples.A.AMethod called with arguments 3,1415926535
    A method impl
    After invocation. Return value 6,283185307
    End of test
        2
  •  2
  •   Chris Taylor    12 年前

    1-您可以从 RealProxy

    class LoggingProxy<T> : RealProxy where T : MarshalByRefObject, new()
    {
      T _innerObject;
    
      public static T Create()
      {
        LoggingProxy<T> realProxy = new LoggingProxy<T>();
        T transparentProxy = (T)realProxy.GetTransparentProxy();
        return transparentProxy;
      }
    
      private LoggingProxy() : base(typeof(T))
      {
        _innerObject = new T();
      }
    
      public override IMessage Invoke(IMessage msg)
      {
        if (msg is IMethodCallMessage)
        {
          IMethodCallMessage methodCall  = msg as IMethodCallMessage;
    
          System.Diagnostics.Debug.WriteLine("Enter: " + methodCall.MethodName);
          IMessage returnMessage = RemotingServices.ExecuteMessage(_innerObject, msg as IMethodCallMessage);
          System.Diagnostics.Debug.WriteLine("Exit: " + methodCall.MethodName);
          return returnMessage;
        }
    
        return null;
      }
    }
    

    可按如下方式使用

    class MyClass : MarshalByRefObject
    {
      public int Age
      {
        get;
        set;
      }
    }
    
    MyClass o = LoggingProxy<MyClass>.Create();
    o.Age = 10;
    

    上面的代码将记录在MyClass的代理实例上设置年龄的调用。

    2-另一个替代方法,但更多的工作是创建一个代理类,该类动态生成一个从传入的类型派生的类型,并提供基类型中所有方法和属性的实现。生成的方法等将执行日志调用基类实现等,类似于RealProxy示例。使用VSVAR类型,您可能不需要从该类型实际继承,而是使用此代理的聚合,这样您仍然可以获得IntelliSense支持,而不需要将所有方法/属性都设置为虚拟的。对不起,没有例子,现在这个有点太多了。但你可以看看 CodeDom 或者更好 Reflection.Emit 对于动力型建筑。动态代码可以做一些类似于@tvanfosson答案中提出的事情。

    最后你可以用 DynamicObject

    public class DynamicProxy : System.Dynamic.DynamicObject
    {
      private object _innerObject;
      private Type _innerType;
    
      public DynamicProxy(object inner)
      {
        if (inner == null) throw new ArgumentNullException("inner");
        _innerObject = inner;
        _innerType = _innerObject.GetType();
      }
    
      public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result)
      {
        System.Diagnostics.Debug.WriteLine("Enter: ", binder.Name);
    
        try
        {
          result = _innerType.InvokeMember(
            binder.Name,
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod,
            null, _innerObject, args);
        }
        catch (MissingMemberException)
        {
          return base.TryInvokeMember(binder, args, out result);
        }
        finally
        {
          System.Diagnostics.Debug.WriteLine("Exit: ", binder.Name);
        }
    
        return true;
      }
    
      public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
      {
        System.Diagnostics.Debug.WriteLine("Enter: ", binder.Name);
    
        try
        {
        result = _innerType.InvokeMember(
          binder.Name,
          BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
          null, _innerObject, null);
        }
        catch (MissingMemberException)
        {
          return base.TryGetMember(binder, out result);
        }
        finally
        {
          System.Diagnostics.Debug.WriteLine("Exit: ", binder.Name);
        }
    
        return true;
      }    
    
      public override bool TrySetMember(System.Dynamic.SetMemberBinder binder, object value)
      {
        System.Diagnostics.Debug.WriteLine("Enter: ", binder.Name);
    
        try
        {
        _innerType.InvokeMember(
          binder.Name,
          BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
          null, _innerObject, new object[]{ value });
        }
        catch (MissingMemberException)
        {
          return base.TrySetMember(binder, value);
        }
        finally
        {
          System.Diagnostics.Debug.WriteLine("Exit: ", binder.Name);
        }      
    
        return true;
      }
    
      public override string ToString()
      {
        try
        {
          System.Diagnostics.Debug.WriteLine("Enter: ToString");
          return _innerObject.ToString();
        }
        finally
        {
          System.Diagnostics.Debug.WriteLine("Exit: ToString");
        }
      }
    }
    

    它的用法如下

    dynamic o2 = new DynamicProxy(new MyClass());
    o.Age = 10;
    

    有几个选项可以滚动您自己的解决方案,或者您可以查看一些预先备份的解决方案。这可能是一个更好的方法,但这应该让您了解一些解决方案是如何实现的。

        3
  •  1
  •   DeadlyChambers    7 年前

    这对于您的特定用例来说可能太重了,但您可能需要研究Castle Dynamic Proxy:

    Dynamic Proxy

    这个框架允许您在运行时动态地为类创建代理,允许您截获所有调用并注入所需的任何逻辑。

        4
  •  0
  •   tvanfosson    14 年前

    我的方法是使用依赖注入,并将logger实例传递给需要进行日志记录的类。如果您有一个支持基于属性的过滤的框架,比如MVC,那么您也可以使用这些框架,尽管您可能会受限于可以记录的内容。

    public class LoggedClass
    {
         private Logger Logger { get; set; }
    
         public LoggerClass( Logger logger )
         {
              this.Logger = logger;
         }
    
         public void A()
         {
             this.Logger.Info( "A has been called" );
             ...
         }
    }
    

    或者在MVC中,或者一个理解属性并可以在方法调用之前调用它们的合适框架。

    [Log]
    public ActionResult A()
    {
       ...
    }
    
    public class LogAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
             ... use context to log stuff
        }
    }
    

    最后一个选择,我能想到的,你已经说过你不想使用,是装饰图案。在这种情况下,您的代理类和代理类需要实现相同的接口,您只需使用所需的功能包装代理类。请注意,定义接口——并在需要向日志类添加功能时扩展接口——可以防止您忘记扩展代理以使其与日志类保持同步。因为它实现了接口,所以除非它拥有所有的接口方法,否则它不会编译。

    public interface IDoSomething
    {
        void A();
        void B();
    }
    
    public class ToLogClass : IDoSomething
    {
        public void A() { ... }
        public void B() { ... }
    }
    
    public class LoggedClass : IDoSomething
    {
        private IDoSomething Inner { get; set; }
        private Logger Logger { get; set; }
    
        public Proxy( IDoSomething inner, Logger logger )
        {
            this.Inner = inner;
            this.Logger = logger;
        }
    
        public void A()
        {
            this.Logger.Info( "A callsed on {0}", this.Inner.GetType().Name );
            this.Inner.A();
        }
    
    }
    
        5
  •  0
  •   Stuart Lange    14 年前

    另一个选择是面向方面的框架,如PostSharp:

    http://www.sharpcrafters.com/

    这允许您定义注入代码的属性,这些代码将在方法调用期间的某个点被调用(OnEntry、OnExit、OnException等)。