[英]Converting Ruby to C#
需要將以下代碼從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
結束
我完全不知道C#,所以我對C#說的任何話都應該帶着一點點鹽。 但是,我將嘗試解釋那段Ruby代碼中發生的事情。
class << Cache
Ruby有一種稱為單例方法的東西。 這些與Singleton軟件設計模式無關,它們只是為一個且只有一個對象定義的方法。 因此,您可以擁有同一個類的兩個實例,並將方法添加到這兩個對象之一。
單例方法有兩種不同的語法。 一種是使用對象前綴方法的名稱,因此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
,因為這是它們的使用方式。
# 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的真相系統。 在Ruby中,事實非常簡單。 實際上,虛假很簡單,而且事實很自然:特殊關鍵字false
為false,特殊關鍵字nil
為false,其他一切都為真。 因此,在這種情況下,如果ttl
為false
或nil
,則條件將僅為 true。 對於一個時間跨度而言, false
不是一個可怕的合理價值,所以唯一有趣的是nil
。 片段會更清楚地寫成: if ttl.nil? then return get(key) { yield } end
if ttl.nil? then return get(key) { yield } end
。 由於ttl
參數的默認值為nil
,因此如果沒有為ttl
傳入參數,則此條件為true。 因此,條件用於計算方法被調用的參數數量,這意味着我們不會將其轉換為條件,而是將其轉換為方法重載。
現在, yield
。 在Ruby中,每個方法都可以接受隱式代碼塊作為參數。 這就是為什么我在上面寫道,該方法實際上需要四個參數,而不是三個。 代碼塊只是一段匿名代碼,可以傳遞,存儲在變量中,以后再調用。 Ruby繼承了Smalltalk的塊,但這個概念可以追溯到1958年,直到Lisp的lambda表達式。 在提到匿名代碼塊時,但至少現在,在提及lambda表達式時,您應該知道如何表示這個隱式的第四個方法參數:委托類型,更具體地說是Func
。
那么, yield
是多少呢? 它將控制轉移到塊。 它基本上只是一種調用塊的非常方便的方式,而不必將其顯式存儲在變量中然后調用它。
# 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
這是我將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);
}
請注意,我不知道C#,我不知道.NET,我沒有測試過這個,我甚至不知道它是否在語法上有效。 無論如何希望它有所幫助。
如果緩存不包含所請求的數據,那么看起來這個代碼正在傳遞一個要評估的塊( yield
就是你如何調用塊)。 這是相當慣用的紅寶石代碼; 我不知道你是怎么(或者甚至)將它“翻譯”成c#。
尋找一個用例來看我的意思。 你應該找到像這樣模糊的東西:
x = smart_get([:foo,"bar"]) { call_expensive_operation_foo("bar") }
更好的選擇是弄清楚你需要它做什么,然后寫一些在c#中重新創建的東西,而不是試圖從ruby中“翻譯”。
看起來你正試圖將memcache-client從Ruby移植到C#。 如果是這樣,使用本機C#memcache客戶端實現可能更容易,例如:
http://code.google.com/p/beitmemcached/
無論哪種方式,我普遍認同MarkusQ,從高級語言到低級語言的翻譯可能總體上不如僅僅以目標語言的慣用方式重寫。 從Ruby到C#的直接翻譯最多會給你一些非常難看的代碼。
yield關鍵字允許您調用作為方法的隱式聲明參數傳遞的代碼塊。 因此,例如,您可以將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 }
大括號中的內容被分配給上面擴展定義中的變量塊。 然后yield調用&block中的代碼。 這是一個嚴格的簡化,但應該幫助您獲得一般的想法。
回到你的轉換。 我剛剛做的簡化並不一定會讓你100%在那里,因為只要你找到一種C#方式來翻譯代碼,另一段代碼就會破壞你的翻譯。 例如,假設一個給定的方法需要檢查塊:
def foo
if block.arity == 0
# No arguments passed, load defaults from config file and ignore call
else
yield
end
end
當然,由於lambdas是Ruby中的第一類對象,您可能會遇到如下代碼:
foo = lambda { |a, b, c| a + b + c }
# foo is now defined as a function that sums its three arguments
如果您遇到動態定義方法的代碼,或者(更糟糕的是翻譯)利用Ruby的可延展類,上帝會幫助您:
class Foo
def show
puts "Foo"
end
end
foo = Foo.new
foo.show # prints "Foo"
class <<foo; def show; puts "Bar"; end; end
foo.show # prints "Bar"
Foo.new.show # prints "Foo"
foo.show # Still prints "Bar"
因為Ruby中每個類的每個實例都有自己的類定義,所以我不確定如何在沒有花哨的體操的情況下將簡單的例子移植到C#。 Ruby有很多這些功能會破壞簡單的翻譯工作,所以我建議你學習你想要移植的東西,然后從頭開始重做。
嘗試這個:
def foo
if block.arity == 0
# No arguments passed, load defaults from config file and ignore call
else
yield
end
end
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.