나는이 문제를 여러 번 직면했으며 간단한 해결책을 생각해 냈다고 생각합니다.
처음에는 데코레이터 패턴을 사용하고 각 방법을 수동으로 구현했습니다. 수백 가지의 방법이 있으면 매우 지루합니다.
그런 다음 PostSharp를 사용하기로 결정했지만 간단한 코드로 많은 작업을 수행하기 위해 전체 라이브러리를 포함시키는 아이디어가 마음에 들지 않았습니다.
그런 다음 재미 있지만 런타임에 동적으로 IL을 방출하는 투명한 프록시 경로를 내려 갔으며 프로덕션 환경에서하고 싶지 않은 작업이었습니다.
최근에는 디자인 타임에 데코레이터 패턴을 자동으로 구현하기 위해 T4 템플릿을 사용하기로 결정했습니다 .T4 템플릿은 실제로 작업하기가 매우 어려우며 신속하게 완료해야 코드를 만들었습니다. 빠르고 더러 우며 속성을 지원하지 않지만 누군가가 유용하게 사용할 수 있기를 바랍니다.
코드는 다음과 같습니다.
var linesToUse = code.Split(Environment.NewLine.ToCharArray()).Where(l => !string.IsNullOrWhiteSpace(l));
string classLine = linesToUse.First();
// Remove the first line this is just the class declaration, also remove its closing brace
linesToUse = linesToUse.Skip(1).Take(linesToUse.Count() - 2);
code = string.Join(Environment.NewLine, linesToUse).Trim()
.TrimStart("{".ToCharArray()); // Depending on the formatting this may be left over from removing the class
code = Regex.Replace(
code,
@"public\s+?(?'Type'[\w<>]+?)\s(?'Name'\w+?)\s*\((?'Args'[^\)]*?)\)\s*?\{\s*?(throw new NotImplementedException\(\);)",
new MatchEvaluator(
match =>
{
string start = string.Format(
"public {0} {1}({2})\r\n{{",
match.Groups["Type"].Value,
match.Groups["Name"].Value,
match.Groups["Args"].Value);
var args =
match.Groups["Args"].Value.Split(",".ToCharArray())
.Select(s => s.Trim().Split(" ".ToCharArray()))
.ToDictionary(s => s.Last(), s => s.First());
string call = "_decorated." + match.Groups["Name"].Value + "(" + string.Join(",", args.Keys) + ");";
if (match.Groups["Type"].Value != "void")
{
call = "return " + call;
}
string argsStr = args.Keys.Any(s => s.Length > 0) ? ("," + string.Join(",", args.Keys)) : string.Empty;
string loggedCall = string.Format(
"using (BuildLogger(\"{0}\"{1})){{\r\n{2}\r\n}}",
match.Groups["Name"].Value,
argsStr,
call);
return start + "\r\n" + loggedCall;
}));
code = classLine.Trim().TrimEnd("{".ToCharArray()) + "\n{\n" + code + "\n}\n";
예를 들면 다음과 같습니다.
public interface ITestAdapter : IDisposable
{
string TestMethod1();
IEnumerable<string> TestMethod2(int a);
void TestMethod3(List<string[]> a, Object b);
}
그런 다음 ITestAdapter를 구현하는 LoggingTestAdapter라는 클래스를 작성하고 Visual Studio에서 모든 메소드를 자동으로 구현 한 다음 위의 코드를 통해 실행하십시오. 그러면 다음과 같은 것이 있어야합니다.
public class LoggingTestAdapter : ITestAdapter
{
public void Dispose()
{
using (BuildLogger("Dispose"))
{
_decorated.Dispose();
}
}
public string TestMethod1()
{
using (BuildLogger("TestMethod1"))
{
return _decorated.TestMethod1();
}
}
public IEnumerable<string> TestMethod2(int a)
{
using (BuildLogger("TestMethod2", a))
{
return _decorated.TestMethod2(a);
}
}
public void TestMethod3(List<string[]> a, object b)
{
using (BuildLogger("TestMethod3", a, b))
{
_decorated.TestMethod3(a, b);
}
}
}
다음은 지원 코드입니다.
public class DebugLogger : ILogger
{
private Stopwatch _stopwatch;
public DebugLogger()
{
_stopwatch = new Stopwatch();
_stopwatch.Start();
}
public void Dispose()
{
_stopwatch.Stop();
string argsStr = string.Empty;
if (Args.FirstOrDefault() != null)
{
argsStr = string.Join(",",Args.Select(a => (a ?? (object)"null").ToString()));
}
System.Diagnostics.Debug.WriteLine(string.Format("{0}({1}) @ {2}ms", Name, argsStr, _stopwatch.ElapsedMilliseconds));
}
public string Name { get; set; }
public object[] Args { get; set; }
}
public interface ILogger : IDisposable
{
string Name { get; set; }
object[] Args { get; set; }
}
public class LoggingTestAdapter<TLogger> : ITestAdapter where TLogger : ILogger,new()
{
private readonly ITestAdapter _decorated;
public LoggingTestAdapter(ITestAdapter toDecorate)
{
_decorated = toDecorate;
}
private ILogger BuildLogger(string name, params object[] args)
{
return new TLogger { Name = name, Args = args };
}
public void Dispose()
{
_decorated.Dispose();
}
public string TestMethod1()
{
using (BuildLogger("TestMethod1"))
{
return _decorated.TestMethod1();
}
}
public IEnumerable<string> TestMethod2(int a)
{
using (BuildLogger("TestMethod2", a))
{
return _decorated.TestMethod2(a);
}
}
public void TestMethod3(List<string[]> a, object b)
{
using (BuildLogger("TestMethod3", a, b))
{
_decorated.TestMethod3(a, b);
}
}
}