2016-10-26 2 views
11

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

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

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

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

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

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

+5

Возможно, вы захотите посмотреть на решения AOP, такие как PostSharp для этого типа вещей. –

+0

Также рассмотрите группу методов [Tuple.Create] (https://msdn.microsoft.com/en-us/library/system.tuple.create (v = vs.110) .aspx). Невозможно написать методы, допускающие произвольное количество генериков. –

+0

Вызывающий может тривиально передавать любой вызов как «действие», просто делая его выражением: 'timedAction (« methodname »,() => {lib.methodname (1, 2, 3);})'. –

ответ

5

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

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

Возвращаемое значение 'func()' всегда имеет тип 'TValue', я не думаю, что вам нужно отбросить его. – thehennyy

+0

Вы правы. Я отредактировал свой ответ. – selami

+0

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

1

Действительно, АОП будет покупать вам больше, чем такого рода занудства:

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; было слишком лениво, чтобы положить его туда)

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

+2

Я не могу понять, почему вы заменили бы существующий код «Секундомера» OP на «DateTime» и назовите этот дополнительный шаг лень. – Groo

+0

@Groo. Я тоже был на моем телефоне, и dotnetfiddle продолжал рушиться на меня с помощью пространства имен системной диагностики. Благодарю. – YSharp

1

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

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

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

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

enter image description here

0

Я знаю, что этот вопрос уже есть ответ, но я думаю, что это решение может быть интересным, если вы не хотите, чтобы вам нужно было пропустить это имя, каждый раз, вы могли бы сделать это:
(Это было очень вдохновлено ответом @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); 
} 

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