Bu sorunla pek çok kez karşılaştım ve sanırım basit bir çözüm buldum.
Başlangıçta dekoratör paternine gittim ve her metodu elle uyguladım, yüzlerce metot varken bu çok sıkıcı oluyor.
Daha sonra PostSharp'ı kullanmaya karar verdim ancak basit bir kodla başarabileceğim bir şeyi yapmak için kütüphanenin tamamını dahil etme fikrini beğenmedim.
Daha sonra eğlenceli olan ancak çalışma zamanında IL'yi dinamik olarak yayan ve prodüksiyon ortamında yapmak istediğim bir şey olmayacak olan şeffaf proxy yolundan indim.
Geçenlerde dekoratör kalıbını tasarım zamanında otomatik olarak uygulamak için T4 şablonlarını kullanmaya karar verdim, sonuçta T4 şablonlarının çalışmasının oldukça zor olduğu ortaya çıktı ve hızlıca bu işlemi yapmam gerekiyordu. Çabuk ve kirli (ve özellikleri desteklemiyor) ancak umarım birileri onu yararlı bulacaktır.
İşte kod:
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";
İşte bir örnek:
public interface ITestAdapter : IDisposable
{
string TestMethod1();
IEnumerable<string> TestMethod2(int a);
void TestMethod3(List<string[]> a, Object b);
}
Ardından, ITestAdapter'ı uygulayan LoggingTestAdapter adlı bir sınıf oluşturun, tüm yöntemleri otomatik olarak uygulamak için görsel stüdyosu alın ve ardından yukarıdaki kodda çalıştırın. Öyleyse böyle bir şeye sahip olmalısınız:
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);
}
}
}
Bu destek koduyla birlikte:
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);
}
}
}