Объявление общей функции в С#

Я пытаюсь создать некоторую статистику о длительности вызова метода в библиотеке. Вместо того чтобы обернуть вызов каждого метода в библиотеку с помощью строк по времени и отслеживать его, я хочу создать общее действие и функцию, которые выполняют эти повторяющиеся шаги.

например. для методов, которые не возвращают значение, я создал это:

    private readonly Action<string, Action> timedAction = (name, action) =>
    {
        var sw = Stopwatch.StartNew();
        action.Invoke();
        trackDuration(name, sw.ElapsedMilliseconds);
    };

Это можно вызвать с помощью timedAction("methodname", () => lib.methodname()).

Я хочу сделать что-то подобное для методов, возвращающих значение, но, очевидно, Action не может использоваться для этой цели, поскольку он не может вернуть значение.

Есть ли способ сделать это с помощью общего Func, поэтому мне не нужно объявлять один для каждой комбинации параметров библиотечного метода?

Ответ 1

Вы можете использовать такую ​​общую функцию, как это:

private static TValue FuncHandler<TValue>(string name, Func<TValue> func)
{
    var sw = Stopwatch.StartNew();
    var result = func();

    trackDuration(name, sw.ElapsedMilliseconds);

    return result;
}

Назовите его следующим образом:

var result = FuncHandler("name", () => MyMethod(param1));

Ответ 2

В самом деле, АОП купит вам больше, чем эта скука:

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);

}

(да, я знаю о StopWatch, было просто слишком лениво, чтобы поместить его там)

'Надеюсь, что это поможет.

Ответ 3

В вашем случае АОП будет более утомительным. Вот мое решение, которое работает:

введите описание изображения здесь

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;
    };
}
}

Я работал в режиме отладки, чтобы проверить, может ли консоль выводить в миллисекундах, и работает.

Если вы не запускаете отладку, выполнение будет очень быстрым, а консоль выведет 0.

введите описание изображения здесь

Ответ 4

Я знаю, что у этого вопроса уже есть ответ, но я думаю, что это решение может быть интересным, если вы не хотите, чтобы вам нужно было передавать имя, каждый раз, вы могли бы это сделать:
(Это было очень вдохновлено ответом @selami.)

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;
}

И ваши окончательные методы:

public void TimeMethod(Expression<Action> actionExpression)
{
    TimedMethodInvoke(actionExpression);
}

public TValue TimeMethod<TValue>(Expression<Func<TValue>> funcExpression)
{
    return (TValue)TimedMethodInvoke(funcExpression);
}

Я не тестировал этот вариант, но думаю, вы должны столкнуться с небольшим успехом, но если вы не возражаете об этом и хотите избежать ввода имени каждый раз, это может помочь.