如何代理调用对象的实例

问题描述 投票:3回答:5

编辑:更改了问题标题“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可以有任意数量的方法,因此重载或包装所有方法来添加日志记录是不合适的。

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

谢谢!

c# design-patterns overloading
5个回答
2
投票

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

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

我可以推荐一些关于AOP的内容,这是我的主题:Help and Information about Aspect Oriented Programming

(编辑:因为你很好并且给了我一些积分,这里是DP教程的另一个很酷的链接,它真的做得很好:qazxsw poi;))

这是示例代码:

http://kozmic.pl/archive/2009/04/27/castle-dynamic-proxy-tutorial.aspx

控制台输出

Examples.A.AMethod called with arguments 3,1415926535
A method impl
After invocation. Return value 6,283185307
End of test

2
投票

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

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

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

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

以上将在MyClass的代理实例上记录对set_Age的调用。

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

3-最后你cpuld使用Reflection.Emit来做上面的大部分工作,缺点是你不会有编译时验证方法调用而没有智能。同样,这是一个最小的例子。

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

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


1
投票

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

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

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


0
投票

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

Dynamic Proxy

或者在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" );
         ...
     }
}

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

[Log]
public ActionResult A()
{
   ...
}

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

0
投票

另一种选择是像PostSharp这样的面向方面的框架:

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.