将Ruby转换为C#

fla*_*lar 7 c# ruby caching

需要将以下代码从Ruby转换为C#.但是我对使用yield关键字和Ruby的一般语法感到困惑.任何知道一点点Ruby的人都可以帮忙并转换代码

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
Run Code Online (Sandbox Code Playgroud)

结束

Jör*_*tag 12

我根本不知道C#,所以我对C#说的任何话都应该带着一点点盐.但是,我将尝试解释那段Ruby代码中发生的事情.

class << Cache
Run Code Online (Sandbox Code Playgroud)

Ruby有一种称为单例方法的东西.这些与Singleton软件设计模式无关,它们只是为一个且只有一个对象定义的方法.因此,您可以拥有同一个类的两个实例,并将方法添加到这两个对象之一.

单例方法有两种不同的语法.一种是在方法的名称前加上对象的前缀,因此只为对象def foo.bar(baz)定义一个方法.另一种方法称为打开单例类,它在语法上看起来类似于定义一个类,因为这也是语义上发生的事情:单例方法实际上存在于一个不可见的类中,该类插入到对象与类层次结构中的实际类之间.barfoo

这个语法如下所示:class << foo.这将打开单例类对象,foo并且在该类主体内定义的每个方法都成为对象的单例方法foo.

为什么在这里使用?好吧,Ruby是一种纯粹的面向对象语言,这意味着包括类在内的所有东西都是一个对象.现在,如果可以将方法添加到单个对象,并且类是对象,则这意味着可以将方法添加到单个类中.换句话说,Ruby不需要常规方法和静态方法之间的人为区分(无论如何它们都是欺诈:它们不是真正的方法,只是美化程序).什么是C#中的静态方法,它只是类对象的单例类的常规方法.

所有这一切都解释之间界定的一切只是一个冗长的方式class << Cache及其相应endstatic.

  STALE_REFRESH = 1
  STALE_CREATED = 2
Run Code Online (Sandbox Code Playgroud)

在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)
Run Code Online (Sandbox Code Playgroud)

这个方法有三个参数(实际上有四个,我们将在后面看到确切的原因),其中两个是可选的(ttlgeneration_time).它们都有一个默认值,但是,如果ttl没有真正使用默认值,它更多地作为标记来查明参数是否被传入.

30.secondsActiveSupport库添加到Integer类中的扩展.它实际上没有做任何事情,它只是返回self.在这种情况下使用它只是为了使方法定义更具可读性.(还有其它方法做一些更有益的,例如Integer#minutes,返回self * 60Integer#hours等).我们将以此作为参考,该参数的类型不应该int,而是System.TimeSpan.

    # Fallback to default caching approach if no ttl given
    return get(key) { yield } unless ttl
Run Code Online (Sandbox Code Playgroud)

这包含几个复杂的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的真相系统.在Ruby中,事实非常简单.实际上,虚假很简单,而且事实很明显:特殊关键字false是假的,特殊关键字nil是假的,其他一切都是真的.所以,在这种情况下,条件只会是真的,如果ttlfalse或者nil.false对于时间跨度来说,这不是一个可怕的合理价值,所以唯一有趣的是nil.该片段的写法更加清晰:if ttl.nil? then return get(key) { yield } end.由于ttl参数的默认值为nil,如果没有传入参数,则此条件为true ttl.因此,条件用于计算方法被调用了多少个参数,这意味着我们不会将其转换为条件而是作为方法重载.

现在,到了yield.在Ruby中,每个方法都可以接受隐式代码块作为参数.这就是为什么我在上面写道,该方法实际上需要四个参数,而不是三个.代码块只是一段匿名代码,可以传递,存储在变量中,以后再调用.Ruby继承了Smalltalk的块,但这个概念可以追溯到1958年,直到Lisp的lambda表达式.在提到匿名代码块时,至少现在,在提到lambda表达式时,您应该知道如何表示这个隐式的第四个方法参数:委托类型,更具体地说,a Func.

那么,有什么用yield?它将控制转移到块.它基本上只是一种调用块的非常方便的方式,而不必将其显式存储在变量中然后调用它.

    # Create window for data refresh
    real_ttl = ttl + generation_time * 2
    stale_key = "#{key}.stale"
Run Code Online (Sandbox Code Playgroud)

#{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
Run Code Online (Sandbox Code Playgroud)

这是我将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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,我不知道C#,我不知道.NET,我没有测试过这个,我甚至不知道它是否在语法上有效.无论如何希望它有所帮助.


Mar*_*usQ 5

如果缓存不包含所请求的数据(yield如何调用块),则此代码将被传递给要评估的块.这是相当惯用的红宝石代码; 我不知道你是怎么(或者甚至)将它"翻译"成c#.

寻找一个用例来看我的意思.你应该找到像这样模糊的东西:

x = smart_get([:foo,"bar"]) { call_expensive_operation_foo("bar") }
Run Code Online (Sandbox Code Playgroud)

更好的选择是弄清楚你需要它做什么,然后写一些在c#中重新创建的东西,而不是试图从ruby中"翻译".