Как внедрить / сгенерировать сантехнический код в методы, украшенные атрибутом? - PullRequest
10 голосов
/ 04 октября 2009

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

Допустим, мы начнем с примера по умолчанию:

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

А потом запомнить это:

// Let's 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;
  }
}

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

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

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

Обновление

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

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

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

Update2

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

Ответы [ 4 ]

6 голосов
/ 05 октября 2009

Я наткнулся на этот атрибут memoizer, используя postsharp

3 голосов
/ 04 октября 2009

Я использовал следующую функцию 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 <Т> , но это можно обобщить еще дальше. Еще одно важное замечание: этот код не является потокобезопасным. Если вам нужна безопасность потоков, вам нужно будет синхронизировать доступ на чтение / запись к хеш-таблице внутренней карты.

1 голос
/ 08 октября 2009

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

1 голос
/ 05 октября 2009

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

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