【问题标题】:Dynamic class decorator AOP动态类装饰器 AOP
【发布时间】:2017-10-12 10:59:20
【问题描述】:

背景:我有一个旧类(在下面的代码中表示为 OriginalComponent),我想将日志记录添加到它的方法中。该项目既没有使用 IOC 容器,也没有使用 AOP 功能,因此我想更清楚地了解如何添加 Logging。

我对解决方案的想法:我觉得一个很好的起点是充当装饰器类的动态构造。我的代码如下所示。

但是,我的代码存在一个很大的缺陷: 如果我不使用精确的参数类型调用 OriginalComponent 类中的方法,那么装饰器类将找不到方法。

问题: 有谁知道我如何克服这个缺点并最终让我能够以与编译器在方法调用中允许不精确参数类型类似的不精确方式调用方法。

以下代码是用 LinqPad 5 编写的。

void Main()
{
    var calculator = new OriginalComponent();
    var logCalc = new DecoratorLogging<IComponent, LogEnableAttribute>(calculator);
    dynamic d = logCalc;

    // Does not work
    try
    {
        var ri = d.Add(1, 2);
        Console.WriteLine($"Main: ri: {ri.GetType().Name}::{ri}");
        Console.WriteLine(new string('-', 20));
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine("ri = d.Add(1, 2) failed");
        Console.WriteLine(ex.ExceptionMessages());
        Console.WriteLine(new string('=', 60));
        Console.WriteLine();
    }

    // Works...
    try
    {
        var ri = d.Add(1M, 2M);
        Console.WriteLine($"Main: ri: {ri.GetType().Name}::{ri}");
        Console.WriteLine(new string('-', 20));
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ExceptionMessages());
        Console.WriteLine(new string('=', 60));
        Console.WriteLine();
    }
}

// Define other methods and classes here

/////   Common interface
public interface IComponent { }

/////   OriginalComponent
public class OriginalComponent : IComponent
{
    [LogEnable(true)]
    public decimal Add(decimal a, decimal b) => a + b;
}

/////   DecoratorLogging<TComponent, TAttr>
public class DecoratorLogging<TComponent, TAttr> : DynamicObject, IComponent
    where TComponent : class
    where TAttr : Attribute, IAttributeEnabled
{
    private TComponent _orig;

    public DecoratorLogging(TComponent orig = null) { _orig = orig; }
    public TComponent Orig => _orig;

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var methNm = binder.Name;
        var parmType = args.Select(a => a.GetType()).ToArray();
        MethodInfo methodInfo = null;
        dynamic tyObj = this;
        for (int i = 0; methodInfo == null; ++i)
        {
            try { tyObj = tyObj.Orig; }
            catch (RuntimeBinderException) { throw new Exception($"Method {methNm} was not found."); }
            var ty = tyObj.GetType();
            methodInfo = ty.GetMethod(methNm, parmType);
        }

        result = null;
        Stopwatch sw = null;

        try
        {
            BeforeLoggingConcern(binder, args, methodInfo, out sw);

            if (tyObj.GetType() == _orig.GetType())
                result = methodInfo.Invoke(_orig, args);
            else
                ((dynamic)_orig).TryInvokeMember(binder, args, out result);

            AfterLoggingConcern(binder, result, methodInfo, sw);
        }
        catch (Exception ex)
        {
            ExceptionLoggingConcern(binder, ex, methodInfo, sw);
            throw;
        }

        return true;
    }

    private void BeforeLoggingConcern(InvokeMemberBinder binder, object[] args, MethodInfo mi, out Stopwatch sw)
    {
        sw = null;
        var isEnabled = IsLogEnabled(mi);
        if (!isEnabled) return;

        Console.WriteLine($"Logging Before:  {binder.Name} method was called");
        var sArgs = string.Join(", ", args.Select(a => $"({a.GetType().Name}, {a})").ToArray());
        Console.WriteLine("Logging Aguments: {0}", sArgs);
        Console.WriteLine();
        sw = new Stopwatch();
        sw.Start();
    }

    private void ExceptionLoggingConcern(InvokeMemberBinder binder, Exception ex, MethodInfo mi, Stopwatch sw)
    {
        var isEnabled = IsLogEnabled(mi);
        if (!isEnabled) return;

        if (sw != null)
        {
            sw.Stop();
            Console.WriteLine($"Logging Exception:  {binder.Name} threw an exception after {sw.ElapsedMilliseconds} milliseconds");
        }
        else
            Console.WriteLine($"Logging Exception:  {binder.Name} threw an exception");

        Console.WriteLine($"Logging Internal message:{Environment.NewLine}\t{ex.ExceptionMessages()}");
        Console.WriteLine(new string('*', 10));
        Console.WriteLine();
    }

    private void AfterLoggingConcern(InvokeMemberBinder binder, object result, MethodInfo mi, Stopwatch sw)
    {
        var isEnabled = IsLogEnabled(mi);
        if (!isEnabled) return;

        if (sw != null)
        {
            sw.Stop();
            Console.WriteLine($"Logging After:  {binder.Name} ended after {sw.ElapsedMilliseconds} milliseconds");
        }
        else
            Console.WriteLine($"Logging After:  {binder.Name} ended");

        Console.WriteLine($"Logging resulting in: type: {result.GetType().Name}, value: {result}");
        Console.WriteLine(new string('.', 10));
        Console.WriteLine();
    }

    private bool IsLogEnabled(MethodInfo method)
    {
        var logAttrs = method.GetCustomAttributes<TAttr>(true);
        if (logAttrs == null) return false;
        return logAttrs.Any(a => a.IsEnabled);
    }
}

/////   IAttributeEnabled
public interface IAttributeEnabled
{
    bool IsEnabled { get; }
}

/////   LogEnableAttribute
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class LogEnableAttribute : Attribute, IAttributeEnabled
{
    private bool _logEnabled;
    public LogEnableAttribute(bool logEnabled = true) { _logEnabled = logEnabled; }
    public bool IsEnabled => _logEnabled;
}

/////   Ext (Used to extract all exception messages)
public static class Ext
{
    public static string ExceptionMessages(this Exception ex)
    {
        if (ex.InnerException == null) return ex.Message;
        return string.Join($"{Environment.NewLine}\t", ex.InnerExceptions().Select((a, i) => $"{i}. {a.Message}"));
    }

    public static IEnumerable<Exception> InnerExceptions(this Exception ex)
    {
        for (Exception ix = ex; ix != null; ix = ix.InnerException)
            yield return ix;
    }
}

【问题讨论】:

  • 谢谢 Pankaj。不。我没有,我工作的银行没有将 IL 编织作为其工具的一部分,因此我需要申请它。由于该软件有效,我怀疑我的请愿书是否有效。

标签: c# dynamic decorator aop


【解决方案1】:

我上面的代码的问题是试图使用 OriginalComponent 类中不存在的类型来查找方法。行:

methodInfo = ty.GetMethod(methNm, parmType);

是罪魁祸首。

我需要做的是使用 Type1.IsAssignableFrom(Type2) 来解析调用哪个方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-04
    • 2017-06-19
    • 2011-07-25
    • 2011-12-28
    • 1970-01-01
    • 2011-12-25
    • 1970-01-01
    • 2014-01-14
    相关资源
    最近更新 更多