Reputation: 3461
I'm trying to create some stats about method call duration in a library. Instead of wrapping each method call to the library with lines to time and track it, I want to create a generic action and function which does these recurring steps.
E.g. for methods that don't return a value, I have created this:
private readonly Action<string, Action> timedAction = (name, action) =>
{
var sw = Stopwatch.StartNew();
action.Invoke();
trackDuration(name, sw.ElapsedMilliseconds);
};
That can be invoked with timedAction("methodname", () => lib.methodname())
.
I want to do something similar for methods that return a value, but obviously Action
can't be used for that purpose, since it can't return a value.
Is there a way to do this with a generic Func
, so I don't have to declare one for each combination of library method parameters?
Upvotes: 11
Views: 2426
Reputation: 1561
I know this question already has an answer, but I think this solution can be interesting, if you don't want to have to pass the name, yourself, each time, you could do this:
(It was a lot inspired by @selami' answer.)
private MemberInfo GetMethodName<T>(Expression<T> expression)
{
Expression body = expression.Body;
// You might want to complete this
// depending on which expression you want to use
return ((MethodCallExpression)body).Method.Name;
}
// Works for both Action and Func
private object TimedMethodInvoke<T>(Expression<T> funcExpression)
{
var sw = Stopwatch.StartNew();
var result = ((Delegate)(object)funcExpression.Compile()).DynamicInvoke();
trackDuration(GetMethodName(funcExpression), sw.ElapsedMilliseconds);
return result;
}
And your final methods:
public void TimeMethod(Expression<Action> actionExpression)
{
TimedMethodInvoke(actionExpression);
}
public TValue TimeMethod<TValue>(Expression<Func<TValue>> funcExpression)
{
return (TValue)TimedMethodInvoke(funcExpression);
}
I didn't run a benchmark over this solution, but I guess you should encounter a little performance hit, but if you don't mind about that and want to avoid to type the name each time, this could help.
Upvotes: 1
Reputation: 2498
You can use a generic function like this:
private static TValue FuncHandler<TValue>(string name, Func<TValue> func)
{
var sw = Stopwatch.StartNew();
var result = func();
trackDuration(name, sw.ElapsedMilliseconds);
return result;
}
Call it like this:
var result = FuncHandler("name", () => MyMethod(param1));
Upvotes: 5
Reputation: 417
In your case AOP will be more tedious. Here is my solution which works:
Class1.cs
using System;
namespace ClassLibrary1
{
public class Class1
{
public void WriteNoParam()
{
Console.WriteLine("void");
}
public void WriteWithParam(string name)
{
Console.WriteLine("My name is: " + name);
}
}
}
Program.cs
using System;
namespace ConsoleApplication2
{
using System.Diagnostics;
using System.Reflection;
using ClassLibrary1;
class Program
{
static void Main(string[] args)
{
var prReflection = new TestReflection<Class1>();
var elapsed = prReflection.TestFunc(new Class1(), @"C:\Users\yasir\Documents\visual studio 2013\Projects\ConsoleApplication2\ClassLibrary1\bin\Debug\ClassLibrary1.dll", "WriteNoParam", new string[0]);
Console.WriteLine("Elapsed time for non parameter method: "+elapsed);
elapsed = prReflection.TestFunc(new Class1(), @"C:\Users\yasir\Documents\visual studio 2013\Projects\ConsoleApplication2\ClassLibrary1\bin\Debug\ClassLibrary1.dll", "WriteWithParam", new[]{"Yasir"});
Console.WriteLine("Elapsed time for parameter method: " + elapsed);
Console.ReadLine();
}
}
public class TestReflection<T> where T: class
{
public Func<T, string, string, string[], long> TestFunc = (arg1, s, s2, arr) =>
{
var assembly = Assembly.LoadFile(s);
var type = assembly.GetType(typeof (T).ToString());
long executionTime;
if (type != null)
{
var methodInfo = type.GetMethod(s2);
if (methodInfo != null)
{
ParameterInfo[] parameters = methodInfo.GetParameters();
object classInstance = Activator.CreateInstance(type, null);
var stopWatch = new Stopwatch();
if (parameters.Length == 0)
{
// This works fine
stopWatch.Start();
methodInfo.Invoke(classInstance, null);
return stopWatch.ElapsedMilliseconds;
}
stopWatch.Start();
methodInfo.Invoke(classInstance, arr); ;
return stopWatch.ElapsedMilliseconds;
}
}
return 0;
};
}
}
I have run in debug mode to test if the console is able to output in milliseconds and it works.
If you don't run in debug, execution will be really fast and console will output 0.
Upvotes: 1
Reputation: 1086
Indeed, AOP will buy you more than this sort of tediousness:
https://dotnetfiddle.net/5PLCmM
// Needs to be replicated after Func<T1, TResult>, Func<T1, T2, TResult>, etc, for all the functions arities you'll want to wrap with it
public static TResult Timed<T1, /*T2, etc*/TResult>(out long duration, Func<T1, /*T2, etc*/TResult> func, T1 arg1/*T2 arg2, etc*/)
{
//start timing
var t0 = DateTime.Now;
var result = func(arg1/*, arg2, etc*/);
//end timing
duration = (long)DateTime.Now.Subtract(t0).TotalMilliseconds;
return result;
}
public int Factorial(int n)
{
return n > 0 ? n * Factorial(n - 1) : 1;
}
public int Fibonacci(int n)
{
return n > 1 ? Fibonacci(n - 2) + Fibonacci(n - 1) : n;
}
public static void Main()
{
var program = new Program();
long duration;
var _12bang = Timed(out duration, program.Factorial, 12);
Console.WriteLine("{0}! = {1} in {2} ms", 12, _12bang, duration);
var fib31 = Timed(out duration, program.Fibonacci, 31);
Console.WriteLine("Fib {0} = {1} in {2} ms", 31, fib31, duration);
}
(yes, I know about StopWatch; was just too lazy to put it in there)
'Hope this helps.
Upvotes: 1