2009-04-13 3 views
7

Необходимо преобразовать следующий код из Ruby в C#. Однако я немного озадачен использованием ключевого слова yield и общего синтаксиса Ruby. Может кто-нибудь, что знает немножко Руби, пожалуйста, помочь и конвертировать кодПреобразование Ruby в C#

class < < Cache 
STALE_REFRESH = 1 
STALE_CREATED = 2 

# 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) 
    # Fallback to default caching approach if no ttl given 
    return get(key) { yield } unless ttl 

    # Create window for data refresh 
    real_ttl = ttl + generation_time * 2 
    stale_key = "#{key}.stale" 

    # 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 

конец

ответ

12

Я вообще не знаю 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, я не проверял это, я даже не знаю, синтаксически ли это. Надеюсь, он все равно поможет.

+0

@ Jorg - вы мне поможете, если я отправлю код для преобразования Ruby в C# ??? – maliks

+0

безупречный ответ: хорошо описание + код – trinalbadger587

5

Оказывается, этот код передается в блок должны быть оценены, если кэш не содержит запрашиваемые данные (yield как вы называете блок). Это довольно идиоматический код ruby; Я не знаю, как (или даже если) вы могли бы «перевести его» на C#.

Ищите пример использования, чтобы узнать, что я имею в виду. Вы должны найти что-то смутно, как это:

x = smart_get([:foo,"bar"]) { call_expensive_operation_foo("bar") } 

Лучше всего было бы, чтобы выяснить, что вам нужно это сделать и написать что-то, что делает, что De Novo в C#, а не пытаться «перевести» с рубином.

+0

Можно было бы сделать что-то подобное с лямбда-выражений (см http://msdn.microsoft.com/en-us/library/bb397687.aspx) в .NET> = 3.0. –

+0

Вы могли бы, но переводить тяжелый идиоматический код с одного языка на другой никогда не будет работать так, как вам хотелось бы. Кажется, что это похоже на японские шутки. – MarkusQ

4

Похоже, вы пытаетесь перенести memcache-клиент из Ruby в C#. Если это так, то может быть проще использовать родной реализации клиента C# кэша памяти, таких как:

http://code.google.com/p/beitmemcached/

Так или иначе, я вообще согласен с MarkusQ, что в переводе с языка высокого уровня на язык более низкого уровня является вероятно, будет менее продуктивным в целом, чем просто переписывать в идиоматическом стиле для целевого языка. Прямой перевод с Ruby на C# в лучшем случае даст вам очень уродливый код.

Ключевое слово Ruby позволяет вызывать блок кода, который был передан как неявно объявленный аргумент методу. Так, например, вы можете думать о smart_get определения метода, как на самом деле больше похож:

def smart_get(key, ttl = nil, generation_time = 30.seconds, &block) 

И когда вы звоните smart_get как таковой:

x = smart_get("mykey", my_ttl) { do_some_operation_here } 

Материал в фигурных скобках получает назначение на переменный блок в расширенном определении выше. затем вызывает код в блоке &. Это грубое упрощение, но должно помочь вам получить общую идею.

Обратно к вашему переводу. Упрощение, которое я только что сделал, не обязательно собирается получить вас на 100%, потому что, как только вы найдете способ C# для перевода этого кода, другой фрагмент кода нарушит ваш перевод. Например, предположим, что данный метод должен проверить блок:

def foo 
    if block.arity == 0 
     # No arguments passed, load defaults from config file and ignore call 
    else 
     yield 
    end 
end 

И, конечно же, так как лямбды являются объектами первого класса в Ruby, вы можете столкнуться с кодом, как следующее:

foo = lambda { |a, b, c| a + b + c } 
# foo is now defined as a function that sums its three arguments 

И тогда Бог поможет вам, если вы столкнулись с кодом, который определяет методы на лету, или (хуже Fo переводчика) имеет преимущество ковких классов в Ruby:

class Foo 
    def show 
     puts "Foo" 
    end 
end 

foo = Foo.new 
foo.show # prints "Foo" 

class <&lt;foo; def show; puts "Bar"; end; end 

foo.show # prints "Bar" 

Foo.new.show # prints "Foo" 

foo.show # Still prints "Bar" 

Поскольку каждый экземпляр O f каждый класс в Ruby может иметь собственное определение класса, я не совсем уверен, как вы могли бы переносить даже простые примеры на C# без причудливой гимнастики.У Ruby есть много этих функций, которые сломают простой перевод, поэтому я бы рекомендовал изучить то, что вы пытаетесь выполнить, и затем переделать его с нуля.

0

Попробуйте это:

def foo 
    if block.arity == 0 
     # No arguments passed, load defaults from config file and ignore call 
    else 
     yield 
    end 
end 
Смежные вопросы