Non conosco il C#, quindi qualsiasi cosa che dico di C# dovrebbe essere presa con un pizzico di sale. Tuttavia, cercherò di spiegare cosa succede in quel pezzo di codice Ruby.
class << Cache
Ruby ha qualcosa chiamato metodi singleton. Questi non hanno nulla a che fare con il Singleton Software Design Pattern, sono solo metodi che sono definiti per uno e solo un oggetto. Pertanto, puoi avere due istanze della stessa classe e aggiungere metodi a uno di questi due oggetti.
Esistono due diverse sintassi per i metodi Singleton. Uno è solo il prefisso del nome del metodo con l'oggetto, quindi def foo.bar(baz)
definirebbe un metodo bar
solo per l'oggetto foo
. L'altro metodo si chiama aprendo la classe singleton e sembra sintatticamente simile alla definizione di una classe, perché è anche ciò che accade semanticamente: i metodi singleton vivono in realtà in una classe invisibile che viene inserita tra l'oggetto e la sua classe effettiva nella classe gerarchia.
Questa sintassi è simile a questa: class << foo
. Ciò apre la classe di oggetti singleton foo
e ogni metodo definito all'interno di quel corpo di classe diventa un metodo singleton dell'oggetto foo
.
Perché viene utilizzato qui? Bene, Ruby è un puro linguaggio orientato agli oggetti, il che significa che tutto, incluse le classi è un oggetto. Ora, se i metodi possono essere aggiunti a singoli oggetti e le classi sono oggetti, ciò significa che i metodi possono essere aggiunti a singole classi. In altre parole, Ruby non ha bisogno della distinzione artificiale tra metodi regolari e metodi statici (che sono comunque una frode: non sono realmente metodi, solo procedure glorificate). Qual è un metodo statico in C#, è solo un metodo normale sulla classe Singleton di un oggetto di classe.
Tutto questo è solo un modo prolisso di spiegare che tutto ciò che viene definito tra class << Cache
e il corrispondente end
diventa static
.
STALE_REFRESH = 1
STALE_CREATED = 2
In Ruby, ogni variabile che inizia con una lettera maiuscola, è in realtà una costante. Tuttavia, in questo caso non traduciamo questi come campi static const
, ma piuttosto uno enum
, perché è così che vengono utilizzati.
# 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)
Questo metodo ha tre parametri (quattro in realtà, vedremo esattamente il motivo per cui più tardi), due dei quali sono opzionali (ttl
e generation_time
). Entrambi hanno un valore predefinito, tuttavia, nel caso di ttl
il valore predefinito non è realmente utilizzato, serve più come un indicatore per scoprire se l'argomento è stato passato o meno.
30.seconds
è un'estensione che la libreria ActiveSupport
aggiunge alla classe Integer
. In realtà non fa nulla, restituisce solo self
. È usato in questo caso solo per rendere più leggibile la definizione del metodo. (Esistono altri metodi che fanno qualcosa di più utile, ad esempio Integer#minutes
, che restituisce self * 60
e Integer#hours
e così via.) Useremo questo come indicazione, che il tipo del parametro non dovrebbe essere int
ma piuttosto System.TimeSpan
.
# Fallback to default caching approach if no ttl given
return get(key) { yield } unless ttl
Questo contiene diversi complessi costrutti di Ruby. Cominciamo con il più semplice: i modificatori condizionali finali. Se un corpo condizionale contiene solo un'espressione, il condizionale può essere aggiunto alla fine dell'espressione. Quindi, invece di dire if a > b then foo end
puoi anche dire foo if a > b
. Quindi, quanto sopra è equivalente a unless ttl then return get(key) { yield } end
.
Il prossimo è anche semplice: unless
è solo zucchero sintattico per if not
. Quindi, siamo ora allo if not ttl then return get(key) { yield } end
Terzo è il sistema di verità di Ruby. In Ruby, la verità è piuttosto semplice. In realtà, la falsità è piuttosto semplice e la verità cade naturalmente: la parola chiave speciale false
è falsa e la parola chiave speciale nil
è falsa, tutto il resto è vero. Quindi, in questo caso il condizionale sarà solo, se ttl
è false
o nil
. false
non è un valore sensato terribile per un intervallo di tempo, quindi l'unico interessante è nil
. Lo snippet sarebbe stato scritto in modo più chiaro in questo modo: if ttl.nil? then return get(key) { yield } end
. Poiché il valore predefinito per il parametro ttl
è nil
, questo condizionale è true, se non è stato inoltrato alcun argomento per ttl
. Quindi, il condizionale è usato per capire quanti argomenti è stato chiamato il metodo, il che significa che non lo tradurremo come un condizionale ma piuttosto come un overload di metodi.
Ora, al numero yield
. In Ruby, ogni metodo può accettare un blocco di codice implicito come argomento. Ecco perché ho scritto sopra che il metodo richiede in realtà quattro argomenti, non tre. Un blocco di codice è solo un pezzo di codice anonimo che può essere passato in giro, memorizzato in una variabile e invocato in seguito. Ruby eredita blocchi da Smalltalk, ma il concetto risale al 1958, alle espressioni lambda di Lisp. Alla menzione dei blocchi di codice anonimi, ma per lo meno ora, al momento di menzionare le espressioni lambda, dovresti sapere come rappresentare questo quarto parametro implicito del metodo: un tipo delegato, in particolare uno Func
.
Quindi, che cosa è yield
fare? Trasferisce il controllo al blocco. È fondamentalmente solo un modo molto conveniente di richiamare un blocco, senza doverlo memorizzare esplicitamente in una variabile e quindi chiamarlo.
# Create window for data refresh
real_ttl = ttl + generation_time * 2
stale_key = "#{key}.stale"
Questa sintassi #{foo}
è chiamato stringa interpolazione. Significa "sostituire il token all'interno della stringa con qualsiasi risultato di valutazione dell'espressione tra parentesi". È solo una versione molto concisa di String.Format()
, che è esattamente ciò che stiamo per tradurre.
# 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
Questo è il mio debole tentativo di tradurre la versione di Ruby per 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);
}
prega di notare che non so C#, non so .NET, non l'ho testato questo, io non so nemmeno se sia sintatticamente valido. Spero che aiuti comunque.
@ Jorg - mi aiuteresti se inserisco un codice per la conversione da Ruby a C# ??? – maliks
risposta perfetta: buona spiegazione + codice – trinalbadger587