Pour le plaisir, je joue avec une classe pour répondre facilement aux résultats de la fonction de cache. L'idée de base est que vous pouvez prendre n'importe quelle fonction souhaitée - bien que vous souhaitiez l'utiliser uniquement pour des fonctions relativement coûteuses - et enveloppez-la facilement pour utiliser des recherches de dictionnaire relativement peu coûteuses pour des courses ultérieures avec le même argument. Il n'y a vraiment pas beaucoup à cela:
public class AutoCache<TKey, TValue>
{
public AutoCache(Func<TKey, TValue> FunctionToCache)
{
_StoredFunction = FunctionToCache;
_CachedData = new Dictionary<TKey, TValue>();
}
public TValue GetResult(TKey Key)
{
if (!_CachedData.ContainsKey(Key))
_CachedData.Add(Key, _StoredFunction(Key));
return _CachedData[Key];
}
public void InvalidateKey(TKey Key)
{
_CachedData.Remove(Key);
}
public void InvalidateAll()
{
_CachedData.Clear();
}
private Dictionary<TKey, TValue> _CachedData;
private Func<TKey, TValue> _StoredFunction;
}
comme point de référence, si je l'utilise dans le code réel, le lieu le plus probable que je l'envisage est dans le cadre d'une couche logique commerciale, où j'utilise ce code pour envelopper une méthode dans l'accès des données. couche qui tire simplement les données d'une table de recherche. Dans ce cas, le voyage de base de données serait coûteux par rapport au dictionnaire et il serait presque toujours exactement une valeur "clé" pour la recherche, c'est donc une bonne correspondance. P> P>
4 Réponses :
Un autre nom pour cette mise en cache automatique des résultats de la fonction est la mémoisation. Pour une interface publique, considérons quelque chose sur ces lignes: ... et utilisez simplement un polymorphisme pour stocker T dans un dictionnaire d'objet. P> Extencation de la gamme de délégués pourrait être mis en œuvre via une application de fonction de curry et de fonction partielle. Quelque chose comme ceci: p> car Une autre façon de le faire serait d'utiliser la réflexion pour inspecter le type de délégué et stocker des tuples dans le dictionnaire plutôt que simplement le type d'argument. Un tuple simpliste serait simplement un wrapper de tableau dont la logique de croix et de l'égalité utilisait des comparaisons et des hachages profondes. P> Invalidation pourrait être aidée avec des références faibles, mais la création de dictionnaires avec La sécurité du fil est facilement effectuée en verrouillant sur le dictionnaire interne des événements de mutation, mais avoir un dictionnaire sans verrouillage peut améliorer les performances dans des scénarios fortement simultanés. Ce dictionnaire serait probablement encore plus difficile à créer - il y a une présentation intéressant sur un pour Java cependant. p> p> curry code> tourne les fonctions de plusieurs arguments en fonctions d'arguments uniques (mais qui peuvent renvoyer des fonctions), les valeurs de retour sont Admissible à la mémoire de mémoisation elle-même. P> affectement code> est délicat - Il est préférable de faire avec le soutien du temps d'exécution (les valeurs d'affaiblissement sont beaucoup plus faciles). Je crois qu'il y a des implémentations là-bas. P>
Nice, je vais lire plus sur la mémoisation. Dans ce cas, je ne pense pas que le currying aidera vraiment ici. Oui, il permet d'utiliser ce code pour gérer une fonction avec plusieurs arguments, mais il ne le fait pas d'une manière qui n'est pas évidente pour l'utilisateur de la classe, qui défait le point de la classe.
Étant donné que cela est pour la valeur éducative principalement - vous devez jeter un coup d'œil à la classe d'affaiblissement, ce qui permet au GC de dégager des poignées inutilisées de votre classe dans un environnement multi-fileté. C'est un motif de mise en cache assez courant dans .NET P>
QUI DIT - CAVEZIER Emputer! Chaque cache est différente. En construisant une solution de capture, vous vous retrouvez souvent dans un cas pathologique où votre "cache" est juste un dictionnaire glorifié avec de nombreuses méthodes d'assistance compliquées qui rendent votre code Hard Toread. p>
WOW - Quelle seroie - j'avais récemment posté une question sur clés opaques en C # ... et parce que j'essaie de mettre en œuvre quelque chose lié à la mise en cache des résultats de la fonction. Comme c'est drôle.
Ce type de métaprogrammation peut être délicat avec C # ... surtout parce que les paramètres de type générique peuvent entraîner une duplication de code maladroite. Vous finissez souvent à répéter presque le même code dans plusieurs endroits, avec différents paramètres de type, pour atteindre la sécurité de type. P>
Voici ma variation de votre approche qui utilise mon modèle de clé et des fermetures opaques pour créer des fonctions cachables. L'échantillon ci-dessous démontre le motif avec un ou deux arguments, mais il est relativement facile de s'étendre à plus. Il utilise également des méthodes d'extension pour créer un motif transparent pour envelopper une fonction de fonctionnement avec une fonction CACHABLE à l'aide de la méthode Cette technique présente de nombreuses mêmes limitations que votre approche (sécurité du fil, tenant des références, etc.) - Je soupçonne qu'ils ne sont pas trop difficiles à surmonter - mais cela supporte un moyen facile de s'étendre à plusieurs paramètres et permet aux fonctions cachables d'être complètement substituables avec des personnes ordinaires - car ils ne sont qu'un délégué wrapper. P> Il convient également de noter que si vous créez une deuxième instance du fonctionnement cacheable - vous obtenez un cache séparé. Cela peut être à la fois une force et une faiblesse ... car certaines situations que vous ne pouvez pas vous rendre compte que cela se produit. P> Voici le code: p> ascommable () code>. Les fermetures capturent le cache associé à la fonction - et rendent l'existence transparent envers les autres appelants. P> public interface IFunctionCache
{
void InvalidateAll();
// we could add more overloads here...
}
public static class Function
{
public class OpaqueKey<A, B>
{
private readonly object m_Key;
public A First { get; private set; }
public B Second { get; private set; }
public OpaqueKey(A k1, B k2)
{
m_Key = new { K1 = k1, K2 = k2 };
First = k1;
Second = k2;
}
public override bool Equals(object obj)
{
var otherKey = obj as OpaqueKey<A, B>;
return otherKey == null ? false : m_Key.Equals(otherKey.m_Key);
}
public override int GetHashCode()
{
return m_Key.GetHashCode();
}
}
private class AutoCache<TArgs,TR> : IFunctionCache
{
private readonly Dictionary<TArgs,TR> m_CachedResults
= new Dictionary<TArgs, TR>();
public bool IsCached( TArgs arg1 )
{
return m_CachedResults.ContainsKey( arg1 );
}
public TR AddCachedValue( TArgs arg1, TR value )
{
m_CachedResults.Add( arg1, value );
return value;
}
public TR GetCachedValue( TArgs arg1 )
{
return m_CachedResults[arg1];
}
public void InvalidateAll()
{
m_CachedResults.Clear();
}
}
public static Func<A,TR> AsCacheable<A,TR>( this Func<A,TR> function )
{
IFunctionCache ignored;
return AsCacheable( function, out ignored );
}
public static Func<A, TR> AsCacheable<A, TR>( this Func<A, TR> function, out IFunctionCache cache)
{
var autocache = new AutoCache<A,TR>();
cache = autocache;
return (a => autocache.IsCached(a) ?
autocache.GetCachedValue(a) :
autocache.AddCachedValue(a, function(a)));
}
public static Func<A,B,TR> AsCacheable<A,B,TR>( this Func<A,B,TR> function )
{
IFunctionCache ignored;
return AsCacheable(function, out ignored);
}
public static Func<A,B,TR> AsCacheable<A,B,TR>( this Func<A,B,TR> function, out IFunctionCache cache )
{
var autocache = new AutoCache<OpaqueKey<A, B>, TR>();
cache = autocache;
return ( a, b ) =>
{
var key = new OpaqueKey<A, B>( a, b );
return autocache.IsCached(key)
? autocache.GetCachedValue(key)
: autocache.AddCachedValue(key, function(a, b));
};
}
}
public class CacheableFunctionTests
{
public static void Main( string[] args )
{
Func<string, string> Reversal = s => new string( s.Reverse().ToArray() );
var CacheableReverse = Reversal.AsCacheable();
var reverse1 = CacheableReverse("Hello");
var reverse2 = CacheableReverse("Hello"); // step through to prove it uses caching
Func<int, int, double> Average = (a,b) => (a + b)/2.0;
var CacheableAverage = Average.AsCacheable();
var average1 = CacheableAverage(2, 4);
var average2 = CacheableAverage(2, 4);
}
}
J'utilise cette extension simple, qui utilise dans ce cas MemoryCache: exemple / Utilisation: (la durée de cache est de 42 secondes): P> public class CachedCalculator
{
private Func<int, int> _heavyExpensiveMultiplier;
public Calculator(Func<int,int> heavyExpensiveMultiplier )
{
//wrap function into cached one
this._heavyExpensiveMultiplier
= heavyExpensiveMultiplier.Cached(x =>/*key for cache*/ x.ToString(), TimeSpan.FromSeconds(42));
}
//this uses cached algorithm
public int Compute(int x)
{
return _heavyExpensiveMultiplier(x);
}
}