Я совсем не знаю C #, поэтому все, что я говорю о C #, следует воспринимать с небольшим количеством соли. Однако я попытаюсь объяснить, что происходит в этом фрагменте кода Ruby.
class << Cache
В Ruby есть нечто, называемое одноэлементными методами . Они не имеют ничего общего с шаблоном проектирования программного обеспечения Singleton, это просто методы, которые определены для одного и только одного объекта. Таким образом, вы можете иметь два экземпляра одного класса и добавлять методы к одному из этих двух объектов.
Существует два разных синтаксиса для одноэлементных методов. Одним из них является просто добавление имени метода к объекту, поэтому def foo.bar(baz)
будет определять метод bar
только для объекта foo
. Другой метод называется , открывающий одноэлементный класс , и он синтаксически похож на определение класса, потому что это также то, что происходит семантически: на самом деле одноэлементные методы живут в невидимом классе, который вставляется между объектом и его фактическим класс в иерархии классов.
Этот синтаксис выглядит следующим образом: class << foo
. Это открывает одноэлементный класс объекта foo
, и каждый метод, определенный внутри этого тела класса, становится одноэлементным методом объекта foo
.
Почему это используется здесь? Ну, Ruby - это чисто объектно-ориентированный язык, что означает, что все , включая классы, является объектом. Теперь, если методы могут быть добавлены к отдельным объектам, а классы являются объектами, это означает, что методы могут быть добавлены к отдельным классам. Другими словами, в Ruby нет необходимости в искусственном различении между обычными и статическими методами (которые, во всяком случае, являются мошенничеством: на самом деле это не методы, а прославленные процедуры). Что такое статический метод в C #, это обычный метод для одноэлементного класса объекта класса.
Все это просто изнурительный способ объяснить, что все, что определено между class << Cache
и соответствующими end
, становится static
.
STALE_REFRESH = 1
STALE_CREATED = 2
В Ruby каждая переменная, которая начинается с заглавной буквы, на самом деле является константой. Однако в этом случае мы не будем переводить их как static const
поля, а скорее enum
, потому что именно так они используются.
# 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)
Этот метод имеет три параметра (на самом деле четыре, мы точно увидим , почему позже), два из них являются необязательными (ttl
и generation_time
). У обоих из них есть значение по умолчанию, однако, в случае ttl
значение по умолчанию на самом деле не используется, оно служит скорее маркером для определения, был ли передан аргумент или нет.
30.seconds
- это расширение, которое библиотека ActiveSupport
добавляет к классу Integer
. На самом деле он ничего не делает, он просто возвращает self
. В этом случае он используется только для того, чтобы сделать определение метода более читабельным. (Существуют другие методы, которые делают что-то более полезное, например, Integer#minutes
, который возвращает self * 60
и Integer#hours
и т. Д.) Мы будем использовать это как указание, что тип параметра не должен быть int
а точнее System.TimeSpan
.
# Fallback to default caching approach if no ttl given
return get(key) { yield } unless ttl
Содержит несколько сложных конструкций Ruby. Начнем с самого простого: конечные условные модификаторы. Если условное тело содержит только одно выражение, то условное выражение может быть добавлено в конец выражения. Таким образом, вместо того, чтобы сказать if a > b then foo end
, вы также можете сказать foo if a > b
. Итак, вышесказанное эквивалентно unless ttl then return get(key) { yield } end
.
Следующее также легко: unless
- просто синтаксический сахар для if not
. Итак, мы сейчас находимся на if not ttl then return get(key) { yield } end
Третье - система правды Руби. В Ruby правда довольно проста. На самом деле ложность довольно проста, и истина естественна: специальное ключевое слово false
ложно, а специальное ключевое слово nil
ложно, все остальное верно. Таким образом, в этом случае условное будет только истинным, если ttl
равно false
или nil
. false
не является ужасным разумным значением для временного интервала, поэтому единственное интересное - это nil
. Фрагмент был бы написан более четко так: if ttl.nil? then return get(key) { yield } end
. Поскольку значение по умолчанию для параметра ttl
равно nil
, это условие имеет значение true, если для ttl
не было передано ни одного аргумента. Таким образом, условное выражение используется для определения количества аргументов, вызванных методом, что означает, что мы не будем переводить его как условное выражение, а скорее как перегрузку метода.
Теперь перейдем к yield
. В Ruby каждый метод может принимать неявный блок кода в качестве аргумента. Вот почему я писал выше, что метод на самом деле принимает четыре аргумента, а не три. Блок кода - это просто анонимный фрагмент кода, который можно передать, сохранить в переменной и вызвать позже. Ruby наследует блоки от Smalltalk, но концепция восходит к 1958 году, к лямбда-выражениям Лиспа. При упоминании анонимных блоков кода, но по крайней мере сейчас, при упоминании лямбда-выражений, вы должны знать, как представить этот неявный четвертый параметр метода: тип делегата, точнее, Func
.
Итак, что делает yield
? Он передает управление блоку. Это в основном просто очень удобный способ вызова блока, без необходимости явно сохранять его в переменной и затем вызывать его.
# Create window for data refresh
real_ttl = ttl + generation_time * 2
stale_key = "#{key}.stale"
Этот синтаксис #{foo}
называется интерполяция строк . Это означает «заменить токен внутри строки любым результатом вычисления выражения между фигурными скобками». Это просто очень краткая версия String.Format()
, и именно это мы и собираемся перевести.
# 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
Это моя слабая попытка перевести версию 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);
}
Обратите внимание, что я не знаю C #, я не знаю .NET, я не проверял это, я даже не знаю, является ли он синтаксически допустимым. Надеюсь, это поможет в любом случае.