将 Ruby 转换为 C#

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/744620/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 23:31:05  来源:igfitidea点击:

Converting Ruby to C#

c#rubycaching

提问by flalar

Need to convert the following code from Ruby to C#. However I'm kind of puzzled by the use of the yield keyword and the general syntax of Ruby. Can anyone that knows a little bit Ruby please help out and convert the code

需要将以下代码从 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

end

结尾

采纳答案by J?rg W Mittag

I don't know C# at all, so anything I say about C# should be taken with a grain of salt. However, I will try to explain what goes on in that piece of Ruby code.

我根本不懂 C#,所以我对 C# 说的任何话都应该持保留态度。但是,我将尝试解释这段 Ruby 代码中发生的事情。

class << Cache

Ruby has something called singleton methods. These have nothing to do with the Singleton Software Design Pattern, they are just methods that are defined for one and only one object. So, you can have two instances of the same class, and add methods to one of those two objects.

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

There are two different syntaxes for singleton methods. One is to just prefix the name of the method with the object, so def foo.bar(baz)would define a method baronly for object foo. The other method is called opening up the singleton classand it looks syntactically similar to defining a class, because that's also what happens semantically: singleton methods actually live in an invisible class that gets inserted between the object and its actual class in the class hierarchy.

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

This syntax looks like this: class << foo. This opens up the singleton class of object fooand every method defined inside of that class body becomes a singleton method of object foo.

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

Why is this used here? Well, Ruby is a pure object-oriented language, which means that everything, including classes is an object. Now, if methods can be added to individual objects, and classes are objects, this means that methods can be added to individual classes. In other words, Ruby has no need for the artificial distinction between regular methods and static methods (which are a fraud, anyway: they aren't really methods, just glorified procedures). What is a static method in C#, is just a regular method on a class object's singleton class.

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

All of this is just a longwinded way of explaining that everything defined between class << Cacheand its corresponding endbecomes static.

所有这一切都仅仅是一个解释之间定义的所有的长篇大论方式class << Cache及其相应endstatic

  STALE_REFRESH = 1
  STALE_CREATED = 2

In Ruby, every variable that starts with a capital letter, is actually a constant. However, in this case we won't translate these as static constfields, but rather an enum, because that's how they are used.

在 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)

This method has three parameters (four actually, we will see exactly whylater), two of them are optional (ttland generation_time). Both of them have a default value, however, in the case of ttlthe default value isn't really used, it serves more as a marker to find out whether the argument was passed in or not.

这个方法有三个参数(实际上是四个,我们稍后会看到确切原因),其中两个是可选的(ttlgeneration_time)。它们都有一个默认值,但是,在ttl没有真正使用默认值的情况下,它更多地用作确定参数是否传入的标记。

30.secondsis an extension that the ActiveSupportlibrary adds to the Integerclass. It doesn't actually do anything, it just returns self. It is used in this case just to make the method definition more readable. (There are other methods which do something more useful, e.g. Integer#minutes, which returns self * 60and Integer#hoursand so on.) We will use this as an indication, that the type of the parameter should not be intbut rather System.TimeSpan.

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

This contains several complex Ruby constructs. Let's start with the easiest one: trailing conditional modifiers. If a conditional body contains only one expression, then the conditional can be appended to the end of the expression. So, instead of saying if a > b then foo endyou can also say foo if a > b. So, the above is equivalent to unless ttl then return get(key) { yield } end.

这包含几个复杂的 Ruby 结构。让我们从最简单的开始:尾随条件修饰符。如果条件体仅包含一个表达式,则可以将条件附加到表达式的末尾。所以,if a > b then foo end除了说你还可以说foo if a > b. 所以,上面的等价于unless ttl then return get(key) { yield } end

The next one is also easy: unlessis just syntactic sugar for if not. So, we are now at if not ttl then return get(key) { yield } end

下一个也很简单:unless只是if not. 所以,我们现在在if not ttl then return get(key) { yield } end

Third is Ruby's truth system. In Ruby, truth is pretty simple. Actually, falseness is pretty simple, and truth falls out naturally: the special keyword falseis false, and the special keyword nilis false, everything else is true. So, in this case the conditional will onlybe true, if ttlis either falseor nil. falseisn't a terrible sensible value for a timespan, so the only interesting one is nil. The snippet would have been more clearly written like this: if ttl.nil? then return get(key) { yield } end. Since the default value for the ttlparameter is nil, this conditional is true, if no argument was passed in for ttl. So, the conditional is used to figure out with how many arguments the method was called, which means that we are not going to translate it as a conditional but rather as a method overload.

第三是Ruby的真值系统。在 Ruby 中,真相非常简单。其实,false假很简单,真理自然就出来了:特殊关键字是假的,特殊关键字nil是假的,其他的都是真的。因此,在这种情况下,条件ttlfalse时才为真nilfalse对于一个时间跨度来说并不是一个可怕的合理值,所以唯一有趣的是nil. 该片段将被更清楚这样写的:if ttl.nil? then return get(key) { yield } end。由于ttl参数的默认值nil是 ,如果没有参数传入 for,则此条件为真ttl. 因此,条件用于计算调用了多少个参数,这意味着我们不会将其转换为条件,而是将其转换为方法重载。

Now, on to the yield. In Ruby, every method can accept an implicit code block as an argument. That's why I wrote above that the method actually takes fourarguments, not three. A code block is just an anonymous piece of code that can be passed around, stored in a variable, and invoked later on. Ruby inherits blocks from Smalltalk, but the concept dates all the way back to 1958, to Lisp's lambda expressions. At the mention of anonymous code blocks, but at the very least now, at the mention of lambda expressions, you should know how to represent this implicit fourth method parameter: a delegate type, more specifically, a Func.

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

So, what's yielddo? It transfers control to the block. It's basically just a very convenient way of invoking a block, without having to explicitly store it in a variable and then calling it.

那么,怎么yield办?它将控制转移到块。它基本上只是调用块的一种非常方便的方式,而无需将其显式存储在变量中然后调用它。

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

This #{foo}syntax is called string interpolation. It means "replace the token inside the string with whatever the result of evaluating the expression between the braces". It's just a very concise version of String.Format(), which is exactly what we are going to translate it to.

这种#{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

This is my feeble attempt at translating the Ruby version to C#:

这是我将 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);
}

Please note that I do not know C#, I do not know .NET, I have not tested this, I don't even know if it is syntactically valid. Hope it helps anyway.

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

回答by MarkusQ

It appears this code is being passed a block to be evaluated if the cache does not contain the requested data (yieldis how you call the block). This is fairly idiomatic ruby code; I don't know how (or even if) you could "translate it" to c#.

如果缓存不包含请求的数据(这yield是您调用块的方式),则此代码似乎正在传递一个要评估的块。这是相当惯用的 ruby​​ 代码;我不知道您如何(甚至是否)可以将它“翻译”为 c#。

Look for a use case to see what I mean. You should find something vaguely like this:

寻找一个用例来了解我的意思。你应该模糊地找到这样的东西:

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

A better bet would be to figure out what you need it to do and write something that does that de novo in c#, rather than trying to "translate" from ruby.

更好的选择是弄清楚你需要它做什么,并在 c# 中编写一些从头开始的东西,而不是试图从 ruby​​ 中“翻译”。

回答by runako

It looks like you're trying to port memcache-client from Ruby to C#. If so, it might be easier to use a native C# memcache client implementation such as:

看起来您正在尝试将 memcache-client 从 Ruby 移植到 C#。如果是这样,使用本机 C# memcache 客户端实现可能会更容易,例如:

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

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

Either way, I generally agree with MarkusQ that translating from a higher-level language to a lower-level language is probably going to be less productive overall than just rewriting in an idiomatic fashion for the target language. A straight translation from Ruby to C# is going to give you some very ugly code, at best.

无论哪种方式,我普遍同意 MarkusQ 的观点,即从高级语言翻译成低级语言可能比仅以目标语言的惯用方式重写整体效率更低。从 Ruby 到 C# 的直接转换最多会给你一些非常丑陋的代码。

The yield Ruby keyword allows you to invoke a block of code that was passed as an implicitly declared argument to the method. So, for example, you can think of the smart_get method definition as actually looking more like:

yield Ruby 关键字允许您调用作为隐式声明参数传递给方法的代码块。因此,例如,您可以认为 smart_get 方法定义实际上看起来更像:

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

And when you call smart_get as such:

当你这样调用 smart_get 时:

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

The stuff in the braces gets assigned to the variable block in the expanded definition above. yield then invokes the code in &block. This is a gross simplification, but should help you get the general idea.

大括号中的内容被分配给上面扩展定义中的变量块。然后 yield 调用 &block 中的代码。这是一个粗略的简化,但应该可以帮助您了解总体思路。

Back to your conversion. The simplification I just did isn't necessarily going to get you 100% there, because as soon as you find a C# way to translate that code, another piece of code will break your translation. For instance, let's say a given method needs to inspect the block:

回到你的转换。我刚刚所做的简化并不一定会让您 100% 到达那里,因为一旦您找到一种 C# 方式来翻译该代码,另一段代码就会破坏您的翻译。例如,假设一个给定的方法需要检查块:

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

And of course since lambdas are first-class objects in Ruby, you may encounter code like the following:

当然,由于 lambda 是 Ruby 中的一流对象,您可能会遇到如下代码:

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

And then God help you if you encounter code that defines methods on the fly, or (worse fo a translator) takes advantages of Ruby's malleable classes:

然后,如果您遇到动态定义方法的代码,或者(对翻译者来说更糟糕)利用 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"

Since each instance of each class in Ruby can have its own class definition, I'm not really sure how you would port even simple examples to C# without fancy gymnastics. Ruby has a lot of these features that will break a simple translation effort, so I would recommend learning the thing you're trying to port, and then redoing it from scratch.

由于 Ruby 中每个类的每个实例都可以有自己的类定义,因此我不太确定如何将简单的示例移植到 C# 中而不需要花哨的技巧。Ruby 有很多这样的特性,这些特性会破坏简单的翻译工作,所以我建议你学习你想要移植的东西,然后从头开始重做。

回答by user3342029

Try this:

尝试这个:

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