Je ne connais pas du tout C#, donc tout ce que je dis à propos de C# devrait être pris avec un grain de sel. Cependant, je vais essayer d'expliquer ce qui se passe dans ce morceau de code Ruby.
class << Cache
Ruby a quelque chose appelé méthodes singleton. Cela n'a rien à voir avec le Singleton Software Design Pattern, ce sont juste des méthodes qui sont définies pour un et un seul objet. Vous pouvez donc avoir deux instances de la même classe et ajouter des méthodes à l'un de ces deux objets.
Il existe deux syntaxes différentes pour les méthodes singleton. L'une consiste simplement à préfixer le nom de la méthode avec l'objet, donc def foo.bar(baz)
définirait une méthode bar
uniquement pour l'objet foo
. L'autre méthode est appelée ouvrant la classe singleton et ressemble syntaxiquement à la définition d'une classe, parce que c'est aussi ce qui se passe sémantiquement: les méthodes singleton vivent réellement dans une classe invisible qui est insérée entre l'objet et sa classe dans la classe hiérarchie.
Cette syntaxe ressemble à ceci: class << foo
. Cela ouvre la classe singleton de l'objet foo
et chaque méthode définie à l'intérieur de ce corps de classe devient une méthode singleton de l'objet foo
.
Pourquoi est-ce utilisé ici? Eh bien, Ruby est un langage orienté objet pur, ce qui signifie que tout, y compris les classes est un objet. Maintenant, si des méthodes peuvent être ajoutées à des objets individuels et si les classes sont des objets, cela signifie que des méthodes peuvent être ajoutées à des classes individuelles. En d'autres termes, Ruby n'a pas besoin de la distinction artificielle entre les méthodes régulières et les méthodes statiques (qui sont une fraude, de toute façon: ce ne sont pas vraiment des méthodes, seulement des procédures glorifiées). Qu'est-ce qu'une méthode statique en C#, est juste une méthode régulière sur la classe singleton d'un objet de classe. Tout cela est juste une façon de dire que tout ce qui est défini entre class << Cache
et end
devient static
.
STALE_REFRESH = 1
STALE_CREATED = 2
En Ruby, toute variable commençant par une majuscule est en fait une constante. Cependant, dans ce cas, nous ne traduirons pas ces champs en static const
, mais plutôt en enum
, parce que c'est ainsi qu'ils sont utilisés.
# Caches data received from a block
#
# The difference between this method and usual Cache.get
# is following: this method caches data and allows user
# to re-generate data when it is expired w/o running
# data generation code more than once so dog-pile effect
# won't bring our servers down
#
def smart_get(key, ttl = nil, generation_time = 30.seconds)
Cette méthode a trois paramètres (quatre en fait, nous verrons exactement pourquoi plus tard), deux d'entre eux sont facultatifs (ttl
et generation_time
). Les deux ont une valeur par défaut, cependant, dans le cas de ttl
la valeur par défaut n'est pas vraiment utilisée, elle sert davantage de marqueur pour savoir si l'argument a été passé ou non.
30.seconds
est une extension que la bibliothèque ActiveSupport
ajoute à la classe Integer
. Il ne fait rien, il renvoie juste self
. Il est utilisé dans ce cas juste pour rendre la définition de la méthode plus lisible. (Il existe d'autres méthodes qui font quelque chose de plus utile, par exemple Integer#minutes
, qui renvoie self * 60
et Integer#hours
et ainsi de suite.Nous utiliserons ceci comme une indication, que le type du paramètre ne doit pas être int
mais plutôt System.TimeSpan
.
# Fallback to default caching approach if no ttl given
return get(key) { yield } unless ttl
Ceci contient plusieurs constructions complexes de Ruby. Commençons par le plus simple: les modificateurs conditionnels à la fin. Si un corps conditionnel contient une seule expression, le conditionnel peut être ajouté à la fin de l'expression. Donc, au lieu de dire if a > b then foo end
, vous pouvez également dire foo if a > b
. Donc, ce qui précède est équivalent à unless ttl then return get(key) { yield } end
.
Le prochain est également facile: unless
est juste du sucre syntaxique pour if not
. Donc, nous sommes maintenant à if not ttl then return get(key) { yield } end
Troisième est le système de vérité de Ruby. En Ruby, la vérité est assez simple. En fait, la fausseté est assez simple, et la vérité tombe naturellement: le mot-clé spécial false
est faux, et le mot-clé spécial nil
est faux, tout le reste est vrai. Donc, dans ce cas, le conditionnel seulement sera vrai, si ttl
est false
ou nil
. false
n'est pas une valeur sensible terrible pour un laps de temps, de sorte que le seul intéressant est nil
. L'extrait aurait été plus clairement écrit comme ceci: if ttl.nil? then return get(key) { yield } end
. Étant donné que la valeur par défaut du paramètre ttl
est nil
, cette condition est vraie si aucun argument n'a été passé pour ttl
. Ainsi, le conditionnel est utilisé pour déterminer avec combien d'arguments la méthode a été appelée, ce qui signifie que nous n'allons pas le traduire comme conditionnel mais plutôt comme une surcharge de méthode.
Maintenant, sur le yield
. Dans Ruby, chaque méthode peut accepter un bloc de code implicite comme argument. C'est pourquoi j'ai écrit ci-dessus que la méthode prend effectivement quatre arguments, pas trois. Un bloc de code est simplement un morceau de code anonyme qui peut être transmis, stocké dans une variable et invoqué plus tard. Ruby hérite des blocs de Smalltalk, mais le concept remonte à 1958, aux expressions lambda de Lisp. A la mention des blocs de code anonymes, mais à tout le moins maintenant, à la mention des expressions lambda, vous devez savoir représenter ce quatrième paramètre de méthode implicite: un type de délégué, plus précisément un Func
.
Alors, qu'est-ce que yield
? Il transfère le contrôle au bloc. C'est fondamentalement un moyen très pratique d'invoquer un bloc, sans devoir le stocker explicitement dans une variable et ensuite l'appeler.
# Create window for data refresh
real_ttl = ttl + generation_time * 2
stale_key = "#{key}.stale"
Cette syntaxe #{foo}
est appelée interpolation de chaîne. Cela signifie "remplacer le jeton à l'intérieur de la chaîne avec le résultat de l'évaluation de l'expression entre les accolades". C'est juste une version très concise de String.Format()
, qui est exactement ce que nous allons traduire.
# Try to get data from memcache
value = get(key)
stale = get(stale_key)
# If stale key has expired, it is time to re-generate our data
unless stale
put(stale_key, STALE_REFRESH, generation_time) # lock
value = nil # force data re-generation
end
# If no data retrieved or data re-generation forced, re-generate data and reset stale key
unless value
value = yield
put(key, value, real_ttl)
put(stale_key, STALE_CREATED, ttl) # unlock
end
return value
end
end
Ceci est ma faible tentative de traduire la version Ruby à C#:
public class Cache<Tkey, Tvalue> {
enum Stale { Refresh, Created }
/* Caches data received from a delegate
*
* The difference between this method and usual Cache.get
* is following: this method caches data and allows user
* to re-generate data when it is expired w/o running
* data generation code more than once so dog-pile effect
* won't bring our servers down
*/
public static Tvalue SmartGet(Tkey key, TimeSpan ttl, TimeSpan generationTime, Func<Tvalue> strategy)
{
// Create window for data refresh
var realTtl = ttl + generationTime * 2;
var staleKey = String.Format("{0}stale", key);
// Try to get data from memcache
var value = Get(key);
var stale = Get(staleKey);
// If stale key has expired, it is time to re-generate our data
if (stale == null)
{
Put(staleKey, Stale.Refresh, generationTime); // lock
value = null; // force data re-generation
}
// If no data retrieved or data re-generation forced, re-generate data and reset stale key
if (value == null)
{
value = strategy();
Put(key, value, realTtl);
Put(staleKey, Stale.Created, ttl) // unlock
}
return value;
}
// Fallback to default caching approach if no ttl given
public static Tvalue SmartGet(Tkey key, Func<Tvalue> strategy) =>
Get(key, strategy);
// Simulate default argument for generationTime
// C# 4.0 has default arguments, so this wouldn't be needed.
public static Tvalue SmartGet(Tkey key, TimeSpan ttl, Func<Tvalue> strategy) =>
SmartGet(key, ttl, new TimeSpan(0, 0, 30), strategy);
// Convenience overloads to allow calling it the same way as
// in Ruby, by just passing in the timespans as integers in
// seconds.
public static Tvalue SmartGet(Tkey key, int ttl, int generationTime, Func<Tvalue> strategy) =>
SmartGet(key, new TimeSpan(0, 0, ttl), new TimeSpan(0, 0, generationTime), strategy);
public static Tvalue SmartGet(Tkey key, int ttl, Func<Tvalue> strategy) =>
SmartGet(key, new TimeSpan(0, 0, ttl), strategy);
}
S'il vous plaît noter que je ne sais pas C#, je ne sais pas .NET, je n'ai pas testé, je Je ne sais même pas si c'est syntaxiquement valide. J'espère que ça aide quand même.
@ Jorg - pouvez-vous m'aider si je poste un code pour Ruby à la conversion C# ??? – maliks
réponse parfaite: bonne explication + code – trinalbadger587