C# 动态类装饰器AOP
Background:我有一个旧类(在下面的代码中表示为OriginalComponent),我想将它添加到它的方法中。该项目既没有使用IOC容器,也没有使用AOP功能,因此我想更清楚地了解如何添加日志记录 我对解决方案的想法:我觉得一个好的起点是动态构造,它将充当装饰器类。下面列出了我的代码 但是,我的代码有一个很大的缺陷: 如果我没有使用精确的参数类型调用OriginalComponent类中的方法,那么decorator类将找不到这些方法 问题: 有人知道我如何克服这个缺点,并最终使我能够以类似于编译器在方法调用中允许不精确参数类型的不精确方式调用方法吗 以下代码是用LinqPad 5编写的C# 动态类装饰器AOP,c#,dynamic,decorator,aop,C#,Dynamic,Decorator,Aop,Background:我有一个旧类(在下面的代码中表示为OriginalComponent),我想将它添加到它的方法中。该项目既没有使用IOC容器,也没有使用AOP功能,因此我想更清楚地了解如何添加日志记录 我对解决方案的想法:我觉得一个好的起点是动态构造,它将充当装饰器类。下面列出了我的代码 但是,我的代码有一个很大的缺陷: 如果我没有使用精确的参数类型调用OriginalComponent类中的方法,那么decorator类将找不到这些方法 问题: 有人知道我如何克服这个缺点,并最终使我能够
void Main()
{
var计算器=新的原始组件();
var logCalc=新的装饰日志(计算器);
动态d=对数;
//不起作用
尝试
{
var ri=d.Add(1,2);
WriteLine($“Main:ri:{ri.GetType().Name}::{ri}”);
Console.WriteLine(新字符串('-',20));
Console.WriteLine();
}
捕获(例外情况除外)
{
Console.WriteLine(“ri=d.Add(1,2)失败”);
Console.WriteLine(例如ExceptionMessages());
WriteLine(新字符串('=',60));
Console.WriteLine();
}
//工作。。。
尝试
{
var ri=d.Add(1M,2M);
WriteLine($“Main:ri:{ri.GetType().Name}::{ri}”);
Console.WriteLine(新字符串('-',20));
Console.WriteLine();
}
捕获(例外情况除外)
{
Console.WriteLine(例如ExceptionMessages());
WriteLine(新字符串('=',60));
Console.WriteLine();
}
}
//在此处定义其他方法和类
/////公共接口
公共接口IComponent{}
/////原始组件
公共类原始组件:IComponent
{
[日志启用(正确)]
公共十进制加法(十进制a、十进制b)=>a+b;
}
/////装饰日志
公共类装饰日志:DynamicObject,IComponent
其中TComponent:class
其中TAttr:Attribute,IAttributeEnabled
{
私有t组件_orig;
公共装饰日志(TComponent orig=null){u orig=orig;}
公共t组件源=>\u源;
public override bool TryInvokeMember(InvokeMemberBinder绑定器,对象[]参数,输出对象结果)
{
var methNm=binder.Name;
var parmType=args.Select(a=>a.GetType()).ToArray();
MethodInfo MethodInfo=null;
动态tyObj=这个;
for(int i=0;methodInfo==null;++i)
{
试试{tyObj=tyObj.Orig;}
catch(RuntimeBinderException){抛出新异常($“未找到方法{methNm}”);}
var ty=tyObj.GetType();
methodInfo=ty.GetMethod(methNm,parmType);
}
结果=空;
秒表sw=null;
尝试
{
记录前关注点(活页夹、参数、方法信息、输出软件);
if(tyObj.GetType()==\u orig.GetType())
结果=methodInfo.Invoke(_orig,args);
其他的
((动态)原始).TryInvokeMember(活页夹、参数、输出结果);
事后记录问题(活页夹、结果、方法信息、软件);
}
捕获(例外情况除外)
{
例外记录问题(活页夹、ex、methodInfo、sw);
投掷;
}
返回true;
}
日志记录前的私有无效关注点(InvokeMemberBinder绑定器、对象[]参数、MethodInfo mi、out秒表sw)
{
sw=null;
var isEnabled=IsLogEnabled(mi);
如果(!isEnabled)返回;
WriteLine($“在调用{binder.Name}方法之前进行日志记录”);
var sArgs=string.Join(“,”,args.Select(a=>$”({a.GetType().Name},{a})”).ToArray();
WriteLine(“日志记录:{0}”,SARG);
Console.WriteLine();
sw=新秒表();
sw.Start();
}
私有无效例外记录关注点(InvokeMemberBinder活页夹、例外示例、MethodInfo mi、秒表sw)
{
var isEnabled=IsLogEnabled(mi);
如果(!isEnabled)返回;
如果(sw!=null)
{
sw.Stop();
WriteLine($“记录异常:{binder.Name}在{sw.elapsedmillesons}毫秒后引发异常”);
}
其他的
WriteLine($“记录异常:{binder.Name}引发异常”);
WriteLine($“记录内部消息:{Environment.NewLine}\t{ex.ExceptionMessages()}”);
WriteLine(新字符串('*',10));
Console.WriteLine();
}
私有void AfterLoggingConcern(InvokeMemberBinder绑定器、对象结果、MethodInfo mi、秒表sw)
{
var isEnabled=IsLogEnabled(mi);
如果(!isEnabled)返回;
如果(sw!=null)
{
sw.Stop();
WriteLine($“在{binder.Name}之后的日志记录在{sw.elapsedmillesons}毫秒之后结束”);
}
其他的
WriteLine($“在{binder.Name}结束后记录”);
WriteLine($“记录结果:类型:{result.GetType().Name},值:{result}”);
Console.WriteLine(新字符串('.',10));
Console.WriteLine();
}
私有布尔值已生成(MethodInfo方法)
{
var logAttrs=method.GetCustomAttributes(true);
if(logAttrs==null)返回false;
返回logAttrs.Any(a=>a.IsEnabled);
}
}
/////无法控制
可启用的公共接口
{
bool已启用{get;}
}
/////LogEnableAttribute
[AttributeUsage(AttributeTargets.Method,Inherited=true)]
公共类LogEnableAttribute:属性,IAttributeEnabled
{
私人楼宇启用;
公共日志属性(bool logE)
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;
}
}
methodInfo = ty.GetMethod(methNm, parmType);