2010-06-05 3 views
2

편집 : "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에는 여러 가지 메서드가있을 수 있으므로 모든 메서드를 오버로드하거나 래핑하여 로깅을 추가하는 것은 적절하지 않습니다.

저는 이것이 이런 종류의 문제에 대한 최선의 접근법이 아닐 수도 있다는 것을 알고 있습니다. 그래서 어떤 사람이 어떤 접근 방법을 사용하는지 알고 있다면 쏴 버리십시오.

감사합니다.

+1

당신은 정말로 무엇을하려고합니까? params (http://msdn.microsoft.com/en-us/library/w5zay9db(VS.71).aspx)를 사용하여 해결할 수 있습니까? – Tom

+0

위에서 쓴 것처럼, 기본적으로 "내부"에있는 메서드가 호출 될 때마다 로그에 메시지를 작성하려고합니다. –

+0

@Tom : 네, 필요한 부분에 가깝습니다. –

답변

2

다른 두 개의 에코; DI는 갈 길입니다. 동적 프록시는 이러한 측면에서 매우 유능합니다.

다음은 필요한 모든 코드를 구현 한 코드 예제입니다. 일반적으로 인터페이스에 대해 코드를 작성하는 것이 좋습니다. Help and Information about Aspect Oriented Programming

(편집 :

나는 AOP에 대해 조금 읽고 추천 할 수는 여기에 내 스레드의 당신은 멋졌고 나에게 몇 가지 포인트를 준 Becuase, 여기 정말 잘 이루어집니다 DP는 튜토리얼 또 다른 멋진 링크입니다 :;

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

콘솔 출력

0,123,043 http://kozmic.pl/archive/2009/04/27/castle-dynamic-proxy-tutorial.aspx)) 여기서

예 코드

+0

고마워, 그게 내가 찾고 있던 바로 그거야! –

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

마지막 대안은, 당신이 이미 사용하지 않을 말한있는, 생각할 수있는, 데코레이터 패턴이다. 이 경우 프록시 클래스와 프록시 된 클래스는 동일한 인터페이스를 구현해야하므로 원하는 기능으로 프록시 된 클래스를 래핑하면됩니다. 로깅 된 클래스에 기능을 추가해야 할 때 인터페이스를 정의하고 확장하면 프록시를 확장하여 로깅 된 클래스와 동기화 상태로 유지하는 것을 잊어 버리지 않도록 할 수 있습니다. 인터페이스를 구현하기 때문에 모든 인터페이스 메소드가 없으면 컴파일되지 않습니다.

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

} 
1

이 너무 특정 사용 사례에 대한 헤비급 될 수 있지만, 당신은 성 동적 프록시를 조사 할 수 있습니다 :

Dynamic Proxy

이 프레임 워크는 동적으로 수업에 대한 프록시를 만들 수 있습니다 런타임을 사용하면 모든 호출을 가로 채고 원하는 모든 논리를 삽입 할 수 있습니다.

+0

나는 이것 두 번째. 내 대답보기. – Henrik

0

또 다른 옵션은 PostSharp 같은 화면 지향 프레임 워크 :

http://www.sharpcrafters.com/

이 메서드 호출하는 동안 특정 지점에서 호출되는 코드를 삽입 속성을 정의 할 수 있습니다 (OnEntry,의 OnExit, OnException , 등).

이 도구의 가장 큰 단점은 바이너리에 대해 컴파일 후 단계를 수행해야한다는 것입니다 (주입은 이 아니며 런타임에 동적으로 수행되었지만이 컴파일 후 단계에서).

2

저는이 문제가 아니라 여러 가지 해결책을 사용합니다.

1- RealProxy에서 사용자 지정 프록시를 파생시키고 .NET Remoting 인프라에서 제공하는 호출 차단을 활용할 수 있습니다. 이점은 매우 쉽지만 제한 사항은 인터페이스를 프록시 처리하고 리플렉션을 사용하여 내부 클래스의 멤버를 호출해야하거나 프록시되는 클래스가 MarshalByRrefObject에서 상속해야한다는 것입니다.

코드 샘플을 제공하는 것을 망설이지 만, 완료되지 않았기 때문에 주저하지만 나는 아마도 불타 올 것입니다. 그러나 여기에 올바른 클래스 등을 가르쳐 줄 수있는 10 분짜리 코드가 있습니다. IMethodCallMessage 만 처리하므로 데모로 작동해야합니다.

class MyClass : MarshalByRefObject 
{ 
    public int Age 
    { 
    get; 
    set; 
    } 
} 

MyClass o = LoggingProxy<MyClass>.Create(); 
o.Age = 10; 

위 MyClass에의 프록시 인스턴스 set_Age로 통화를 기록 할 다음

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

이 사용될 수있다.

2 - 그러나 또 다른 대안은 전달하는 유형에서 파생 된 유형을 동적으로 생성하고 기본 유형의 모든 메소드 및 특성의 구현을 제공하는 프록시 클래스를 작성하는 것입니다. 생성 된 메소드 등은 RealProxy 예제와 비슷한 기본 클래스 구현 등을 호출하는 로깅을 수행합니다. VS var 유형을 사용하면 실제로 유형에서 계승해야하는 필요성을 피할 수 있고이 프록시에 대한 집계를 사용하므로 인텔리 센스 지원이 제공되며 모든 메소드/속성을 가상으로 만들 필요가 없습니다. 죄송합니다. 예를 들어, 지금은 조금 지나치게 많습니다. 하지만 동적 유형의 건물에 대해 CodeDom 이상인 경우 Reflection.Emit을 사용할 수 있습니다. 동적 코드는 @ tvanfosson의 대답에서 제안 된 것과 비슷한 것을 할 수 있습니다.

3 그리고 마지막으로 DynamicObject을 사용하면 단점은 메서드 호출과 intelli-sense의 컴파일 시간 확인을 컴파일하지 않는다는 것입니다. 여기에도 최소한의 예가 있습니다. 다음

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

같은 것을 사용

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

은 다른 방법은 미리 백업 솔루션의 일부를 볼 수 있습니다, 자신의 솔루션을 압연에 대한 몇 가지 옵션이 있습니다. 어느 쪽이 더 좋은 방법일까요? 그러나 이것은 아마도 몇몇 솔루션이 어떻게 구현되었는지에 대한 통찰력을 줄 것입니다.

+0

당신은'TryInvokeMember'에서'inner'를 사용하지 않습니다. 나는 그것이 효과가 있다고 생각하지 않는다. –

+0

@ Jordão - 고마워요. 정확합니다, DynamicProxy 샘플 불완전하고 올바르지 않습니다. 더 완벽한 버전을 추가했지만 강력하게 테스트되지는 않았지만 접근 방법에 대한 아이디어를 제공해야합니다. –

+0

+1 노력에 감사드립니다 ... –

관련 문제