我完全不了解C#,所以我对C#的任何评论都应该持保留态度。然而,我将尝试解释一下这段Ruby代码中发生了什么。
class << Cache
Ruby有一种叫做“单例方法”的东西。它们与单例软件设计模式无关,只是为一个对象定义的方法。因此,您可以拥有相同类的两个实例,并向其中一个对象添加方法。
有两种不同的单例方法语法。一种是仅使用对象前缀方法名称,例如
def foo.bar(baz)
将只为对象
foo
定义一个
bar
方法。另一种方法称为“打开单例类”,它在语法上看起来类似于定义类,因为在类层次结构中,单例方法实际上位于对象和其实际类之间插入的一个不可见类中。
这个语法看起来像这样:
class << foo
。这打开了对象
foo
的单例类,该类体内定义的每个方法都成为对象
foo
的单例方法。
为什么在这里使用它?好吧,Ruby是一种纯面向对象的语言,这意味着
所有东西,包括类都是一个对象。现在,如果可以向单个对象添加方法,并且类是对象,则意味着可以向单个类添加方法。换句话说,Ruby不需要常规方法和静态方法之间的人为区别(它们是欺诈,无论如何:它们实际上不是方法,只是光荣的程序)。在C#中的静态方法只是类对象的单例类上的常规方法。
所有这些都只是解释一切定义在
class << Cache
和其对应的
end
之间的内容都成为
static
的冗长方式。
STALE_REFRESH = 1
STALE_CREATED = 2
在Ruby中,每个以大写字母开头的变量实际上都是常量。然而,在这种情况下,我们不会将它们翻译为
static const
字段,而是作为
enum
来使用,因为这就是它们的用途。
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的真值系统。在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的lambda表达式。在提到匿名代码块时,至少现在你应该知道如何表示这个隐式的第四个方法参数:一个委托类型,更具体地说,是一个Func
。
那么,yield
是什么?它将控制权转移到块中。它基本上只是一种非常方便的调用块的方式,而无需显式地将其存储在变量中,然后调用它。
real_ttl = ttl + generation_time * 2
stale_key = "#{key}.stale"
这个#{foo}
语法被称为字符串插值。它的意思是“用大括号内表达式求值的结果替换字符串中的标记”。它只是String.Format()
的一个非常简洁的版本,我们正好要将其翻译成这个。
value = get(key)
stale = get(stale_key)
unless stale
put(stale_key, STALE_REFRESH, generation_time)
value = nil
end
unless value
value = yield
put(key, value, real_ttl)
put(stale_key, STALE_CREATED, ttl)
end
return value
end
end
这是我尝试将Ruby版本翻译成C#的微小努力:
public class Cache<Tkey, Tvalue> {
enum Stale { Refresh, Created }
public static Tvalue SmartGet(Tkey key, TimeSpan ttl, TimeSpan generationTime, Func<Tvalue> strategy)
{
var realTtl = ttl + generationTime * 2;
var staleKey = String.Format("{0}stale", key);
var value = Get(key);
var stale = Get(staleKey);
if (stale == null)
{
Put(staleKey, Stale.Refresh, generationTime);
value = null;
}
if (value == null)
{
value = strategy();
Put(key, value, realTtl);
Put(staleKey, Stale.Created, ttl)
}
return value;
}
public static Tvalue SmartGet(Tkey key, Func<Tvalue> strategy) =>
Get(key, strategy);
public static Tvalue SmartGet(Tkey key, TimeSpan ttl, Func<Tvalue> strategy) =>
SmartGet(key, ttl, new TimeSpan(0, 0, 30), strategy);
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,我没有测试过这个代码,甚至不知道它是否符合语法规则。但还是希望能对您有所帮助。