如何代理调用对象的实例

时间:2010-06-05 14:28:59

标签: c# design-patterns overloading

编辑:更改了问题标题“C#允许方法重载,PHP样式(__call)吗?” - 弄清楚它与实际问题没什么关系。还编辑了问题文本。

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

现在,我的代码与此类似:

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方式支持)。通过可扩展,意味着每当AnotherClass更改时我都不必修改ProxyClass。

在我的情况下,AnotherClass可以有任意数量的方法,因此重载或包装所有方法来添加日志记录是不合适的。

我知道这可能不是解决此类问题的最佳方法,所以如果有人知道使用什么方法,请拍摄。

谢谢!

5 个答案:

答案 0 :(得分:2)

我使用了许多解决方案来解决这个问题,但类似的事情。

1-您可以从RealProxy派生自定义代理,并利用.NET远程处理基础架构提供的呼叫拦截。优点是这非常简单,但是限制是您需要代理接口并使用反射来调用内部类的成员,或者被代理的类必须从MarshalByRrefObject继承。

虽然我不愿意提供代码示例,但仅仅因为它不完整,我可能会受到抨击。但是这里有一段十分钟的代码,只是为了让你指向正确的类等。当心,我只处理IMethodCallMessage所以这不完整,但应该作为演示。

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的代理实例上记录对set_Age的调用。

2-另一种选择,但更多的工作是创建一个代理类,它动态生成从您传入的类型派生的类型,并提供基类型中所有方法和属性的实现。生成的方法等将执行日志调用基类实现等,类似于RealProxy示例。使用VS var类型,您可以避免实际从类型继承,而是使用此代理的聚合,这样您仍然可以获得智能支持,而不需要将所有方法/属性设置为虚拟。对不起没有例子,现在这有点太多了。但您可以查看使用CodeDom或更好的Reflection.Emit进行动态类型构建。动态代码可以像@tvanfosson的答案那样做。

3-最后你使用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;

有一些选项可用于推出自己的解决方案,或者您可以查看一些预先支持的解决方案。这可能是一种更好的方法,但这应该可以让您对如何实施某些解决方案有所了解。

答案 1 :(得分:2)

与另外两个人相呼应; DI是要走的路。动态代理在这方面非常称职。

这是一些示例代码,完成了所有必需的实现。通常,对接口进行编码是一种很好的做法。

我可以推荐一些关于AOP的内容,这是我的主题: 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 :(得分:1)

对于您的特定用例,这可能太重了,但您可能需要查看Castle Dynamic Proxy:

Dynamic Proxy

此框架允许您在运行时为类动态创建代理,允许您拦截所有调用并注入您想要的任何逻辑。

答案 3 :(得分:0)

我要采用的方法是使用依赖注入并将记录器实例传递给需要进行日志记录的类。如果你有一个支持基于属性的过滤的框架,比如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
    }
}

我能想到的最后一个选择,就是你已经说过你不想使用的,是Decorator模式。在这种情况下,您的代理类和代理类需要实现相同的接口,您只需使用您想要的功能包装代理类。请注意,定义接口 - 并在需要向已记录的类添加功能时扩展它 - 可以防止您忘记扩展代理以使其与记录的类保持同步。由于它实现了接口,因此除非它具有所有接口方法,否则它将无法编译。

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();
    }

}

答案 4 :(得分:0)

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

http://www.sharpcrafters.com/

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

这个工具的一大缺点是它需要你对你的二进制文件运行一个后编译步骤(注入在运行时动态完成,但在这个后编译步骤中)。