Я вообще не знаю C#, поэтому все, что я говорю о C#, следует принимать с солью. Однако я попытаюсь объяснить, что происходит в этом фрагменте кода Ruby.
class << Cache
Рубин имеет нечто, называемое одноэлементные методы. Они не имеют ничего общего с шаблоном проектирования программного обеспечения Singleton, это просто методы, которые определены для одного и только одного объекта. Таким образом, вы можете иметь два экземпляра одного и того же класса и добавлять методы к одному из этих двух объектов.
Существует два разных синтаксиса для одноэлементных методов. Один из них - просто префикс имени метода с объектом, поэтому def foo.bar(baz)
определит метод bar
только для объекта foo
. Другой метод называется , открывающий одноэлементный класс, и он выглядит синтаксически похожим на определение класса, потому что это также происходит семантически: методы singleton фактически живут в невидимом классе, который вставлен между объектом и его фактическим классом в классе иерархия.
Этот синтаксис выглядит следующим образом: 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
, это условие истинно, если аргумент не был принят для ttl
. Таким образом, условное значение используется для определения количества аргументов, вызванных методом, что означает, что мы не будем переводить его как условное, а скорее как перегрузку метода.
Теперь, на yield
. В Ruby каждый метод может принимать неявный блок кода в качестве аргумента. Вот почему я написал выше, что метод фактически принимает четыре аргумента, а не три. Кодовый блок - это просто анонимный фрагмент кода, который можно передавать, хранить в переменной и вызывать позже. Ruby наследует блоки от Smalltalk, но концепция датируется вплоть до 1958 года, к лямбда-выражениям Lisp. При упоминании анонимных блоков кода, но по крайней мере сейчас, при упоминании лямбда-выражений, вы должны знать, как представить этот неявный четвертый параметр метода: тип делегата, более конкретно, Func
.
Итак, что такое yield
do? Он передает управление блоку. Это просто очень удобный способ вызова блока, без необходимости явно хранить его в переменной, а затем вызвать его.
# 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
Это моя слабая попытка перевода версии Руби 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, я не проверял это, я даже не знаю, синтаксически ли это. Надеюсь, он все равно поможет.
@ Jorg - вы мне поможете, если я отправлю код для преобразования Ruby в C# ??? – maliks
безупречный ответ: хорошо описание + код – trinalbadger587