RasmusW
RasmusW

Reputation: 3461

Generic function declaration in C#

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

Answers (4)

romain-aga
romain-aga

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

selami
selami

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

Yasirmx
Yasirmx

Reputation: 417

In your case AOP will be more tedious. Here is my solution which works:

enter image description here

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.

enter image description here

Upvotes: 1

YSharp
YSharp

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

Related Questions