Как вводить/генерировать сантехнический код в методы, украшенные атрибутом?

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

Скажем, мы начинаем с примера по умолчанию:

class Foo
{
  public int Fibonacci(int n)
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  }
}

И затем запомните это:

// Let say we have a utility class somewhere with the following extension method:
// public static Func<TResult> Memoize<TResult>(this Func<TResult> f)

class Foo
{
  public Func<int,int> Fibonacci = fib;

  public Foo()
  {
    Fibonacci = Fibonacci.Memoize();
  }

  public int fib(int n)
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  }
}

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

class Foo
{
  [Memoize]
  public int Fibonacci(int n)
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  }
}

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

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

Спасибо за любые идеи.

Обновление

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

Однако при использовании его в критическом по времени контексте он оказался намного медленнее, чем делегат. Один миллион итераций примера Fibonacci с каждой реализацией привел к более медленному времени выполнения на 80x. (0,012 мс postsharp против 0,00015ms делегата за звонок)

Но, честно говоря, результат вполне приемлем в контексте, в котором я намерен его использовать. Спасибо за ответы!

Update2

Видимо автор PostSharp упорно работает над версии 2.0, который будет включать в себя, помимо всего прочего, повышение производительности в произведенного кода и компиляции время.

Ответ 2

Я использовал следующую функцию Memoize в моем проекте:

public class Foo
{
    public int Fibonacci(int n)
    {
        return n > 1 ? Fibonacci(n - 1) + Fibonacci(n - 2) : n;
    }
}

class Program
{
    public static Func<Т, TResult> Memoize<Т, TResult>(Func<Т, TResult> f) where Т : IEquatable<Т>
    {
        Dictionary<Т, TResult> map = new Dictionary<Т, TResult>();
        return a =>
        {
            TResult local;
            if (!TryGetValue<Т, TResult>(map, a, out local))
            {
                local = f(a);
                map.Add(a, local);
            }
            return local;
        };
    }

    private static bool TryGetValue<Т, TResult>(Dictionary<Т, TResult> map, Т key, out TResult value) where Т : IEquatable<Т>
    {
        EqualityComparer<Т> comparer = EqualityComparer<Т>.Default;
        foreach (KeyValuePair<Т, TResult> pair in map)
        {
            if (comparer.Equals(pair.Key, key))
            {
                value = pair.Value;
                return true;
            }
        }
        value = default(TResult);
        return false;
    }


    static void Main(string[] args)
    {
        var foo = new Foo();
        // Transform the original function and render it with memory
        var memoizedFibonacci = Memoize<int, int>(foo.Fibonacci);

        // memoizedFibonacci is a transformation of the original function that can be used from now on:
        // Note that only the first call will hit the original function
        Console.WriteLine(memoizedFibonacci(3));
        Console.WriteLine(memoizedFibonacci(3));
        Console.WriteLine(memoizedFibonacci(3));
        Console.WriteLine(memoizedFibonacci(3));
    }
}

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

Ответ 3

Чтобы конкретно указать свои баллы:

  • Это было бы слишком сложно сделать в том, как вы описываете, как вам понадобится полномасштабная грамматика С# синтаксический анализатор. Что может быть более жизнеспособным альтернатива пишет управляемое приложение который мог бы загрузить скомпилированный сборка и вытяжка с использованием Reflection. Эта будет включать получение всех типов объекты в данной сборке, глядя для методов по типам, извлечения пользовательский атрибут, а затем испуская код memoization (это часть может быть немного сложной).
  • Если вы идете по маршруту, о котором я упоминаю в № 1, вы могли бы просто добавьте шаг после сборки для запуска вашего инструмента. Visual Studio (который использует MSBuild снизу) делает это относительно легко.

Ответ 4

Если вы пишете плагин PostSharp вместо использования его библиотеки LAOS, вы не получите никакого удара по производительности.