2009-10-04 7 views
10

Je lisais des articles sur la mise en cache et la mémorisation et comment les mettre en œuvre facilement en utilisant des délégués et des génériques. La syntaxe était assez simple, et il est étonnamment facile à implémenter, mais je pense juste à cause de la nature répétitive qu'il devrait être possible de générer du code basé sur un attribut, au lieu de devoir écrire le même code de plomberie encore et encore.Comment injecter/générer du code de plomberie dans des méthodes décorées avec un attribut?

Disons que nous commençons par l'exemple par défaut:

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

Et puis memoize ceci:

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

Je pensais, ça ne serait pas plus simple de faire un générateur de code crache ce code, une fois qu'il trouve une méthode étiquetée qui correspond à l'une des méthodes d'extension Memoize. Donc, au lieu d'écrire ce code de plomberie, je pourrais simplement ajouter un attribut:

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

Honnêtement, je sais que cela ressemble de plus comme le sucre du compilateur qui doit être converti par un préprocesseur que la génération de code réel, mais ma question est:

  1. que pensez-vous est la meilleure façon de trouver les méthodes dans le fichier source aC# qui ont un attribut donné, l'analyse syntaxique les parametertypes et returntype, et la génération d'un délégué qui correspond à cette empreinte
  2. Quelle serait la meilleure façon d'intégrer cela dans le processus de construction, sans réellement sur écrire mon code. Est-il possible de faire un pré-traitement sur les fichiers sources avant de les transmettre au compilateur?

Merci pour toutes les idées.

Mise à jour:

J'ai regardé dans la bibliothèque PostSharp que Shay avait suggéré, et il semblait très adapté pour le travail sur des applications non critiques comme la gestion des transactions, traçage ou de sécurité. Cependant, en l'utilisant dans un contexte critique, il s'est avéré beaucoup plus lent que le délégué. Un million d'itérations de l'exemple Fibonacci avec chaque implémentation a entraîné une exécution 80 fois plus lente. (0.012ms postsharp vs 0.00015ms délégué par appel)

Mais honnêtement, le résultat est tout à fait acceptable dans le contexte dans lequel j'ai l'intention de l'utiliser. Merci pour les réponses!

Update2:

Apparemment, l'auteur de PostSharp travaille dur sur un release 2.0 qui comprendra, entre autres, l'amélioration des performances dans le code produit, et la compilation.

Répondre

6
+0

Cela ressemble à une très bonne alternative. Je vais y regarder. –

+0

J'ai voté pour celui-ci, mais je suis peut-être un peu partial :). –

+0

@Patrick, donc vous faites tout le travail et je reçois les votes :) –

3

Je l'ai utilisé la fonction Memoize suivante dans un projet de la mine:

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

Dans mon projet, je devais ne fonctionne qu'avec un seul argument qui mettent en œuvre IEquatable<Т> mais cela peut être généralisée encore plus loin. Une autre remarque importante est que ce code n'est pas thread-safe.Si vous avez besoin d'une sécurité de thread, vous devrez synchroniser l'accès en lecture/écriture à la hashtable de la carte interne.

+0

Ce n'est pas ma question. Vérifiez les balles en bas. –

1

Pour répondre spécifiquement à vos points:

  1. Ce serait probablement trop difficile à faire de la façon que vous décrivez, comme vous auriez besoin d'un temps plein grammaire C# soufflée analyseur. Ce qui pourrait être une alternative plus viable est d'écrire une application gérée qui pourrait charger l'assemblage compilé et extraire l'information de type en utilisant Reflection. Cette impliquerait d'obtenir tous les objets de type dans un ensemble donné, à la recherche de méthodes sur les types, la récupération l'attribut personnalisé, puis à émettre le code memoization (cette partie pourrait être un peu difficile).
  2. Si vous suivez la route que je mentionne dans # 1, vous pouvez simplement ajouter une étape post-construction pour exécuter votre outil. Visual Studio (qui utilise MSBuild en dessous) rend cela relativement facile.
1

Si vous écrivez un plug-in à PostSharp au lieu d'utiliser sa bibliothèque LAOS, vous n'obtiendrez aucun accès aux performances.

+0

Intéressant, merci! –

Questions connexes