Как прокси-вызовы к экземпляру объекта - PullRequest
3 голосов
/ 05 июня 2010

Редактировать: Изменен заголовок вопроса с "Разрешает ли 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-способом). Расширяемый, что означает, что мне не нужно изменять ProxyClass при каждом изменении AnotherClass.

В моем случае AnotherClass может иметь любое количество методов, поэтому было бы нецелесообразно перегружать или переносить все методы для добавления ведения журнала.

Я знаю, что это может быть не лучшим подходом для решения такого рода проблем, поэтому, если у кого-то есть идея, какой подход использовать, стреляйте.

Спасибо!

Ответы [ 5 ]

2 голосов
/ 06 июня 2010

повторяя два других; D это путь. Динамический прокси очень компетентен в этом отношении.

Вот пример кода, дополненного реализацией всего, что требуется. Обычно рекомендуется использовать код для интерфейса.

Я могу порекомендовать прочитать немного об АОП, вот моя ветка об этом: Справка и информация о аспектно-ориентированном программировании

(Правка: потому что вы были милы и дали мне несколько очков, вот еще одна классная ссылка на учебник 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 голосов
/ 05 июня 2010

Я использовал несколько решений не совсем этой проблемы, но схожие вещи.

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;

Выше будет регистрировать вызов set_Age для проксируемого экземпляра MyClass.

2 - Еще одна альтернатива, но гораздо больше работы - создать прокси-класс, которыйдинамически генерирует тип, производный от передаваемого вами типа, и предоставляет реализации всех методов и свойств базового типа.Сгенерированные методы и т. Д. Будут выполнять регистрацию, вызывать реализацию базового класса и т. Д., Аналогично примеру RealProxy.Используя VS var type, вы, вероятно, можете избежать необходимости наследования от типа и скорее использовать агрегирование для этого прокси-сервера, так что вы все равно будете иметь поддержку intelli-sense и не будете делать все методы / свойства виртуальными.К сожалению, нет примера, это слишком много на данный момент.Но вы можете посмотреть на использование 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 голос
/ 05 июня 2010

Это может быть слишком тяжело для вашего конкретного случая использования, но вы можете посмотреть на Castle Dynamic Proxy:

Динамический прокси

Эта структура позволяет вам динамически создавать прокси для ваших классов во время выполнения, позволяя вам перехватывать все вызовы и вводить любую логику, которую вы хотите.

0 голосов
/ 05 июня 2010

Другим вариантом является Aspect-Oriented Framework, такой как PostSharp:

http://www.sharpcrafters.com/

Это позволяет вам определять атрибуты, которые внедряют код, который будет вызываться в определенный момент во время вызова метода (OnEntry, OnExit, OnException и т. Д.).

Большим недостатком этого инструмента является то, что он требует от вас выполнения этапа посткомпиляции для ваших двоичных файлов ( не выполняется динамически во время выполнения, но на этом этапе после компиляции).

0 голосов
/ 05 июня 2010

Я бы применил инъекцию зависимостей и передачу экземпляра регистратора в класс, который должен вести регистрацию. Если у вас был фреймворк, такой как 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();
    }

}
...