动态类装饰器AOP

时间:2017-05-13 04:54:14

标签: c# dynamic decorator aop

后台:我有一个旧类(在下面的代码中表示为 OriginalComponent ),我想在其方法中添加日志记录。该项目既不使用IOC容器也不使用AOP功能,因此我希望能够更好地添加日志记录。

我对解决方案的想法:我认为一个好的起点是动态构造,它将充当装饰器类。我的代码列在下面。

但是,我的代码存在一个很大的缺陷: 如果我没有使用精确的参数类型调用 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;
    }
}

1 个答案:

答案 0 :(得分:0)

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

-x
罪魁祸首是

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