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
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
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.
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.
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 methodbaronly for objectfoo. The other method is called opening up the singleton class and 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.This syntax looks like this:
class << foo. This opens up the singleton class of objectfooand every method defined inside of that class body becomes a singleton method of objectfoo.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.
All of this is just a longwinded way of explaining that everything defined between
class << Cacheand its correspondingendbecomesstatic.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 anenum, because that’s how they are used.This method has three parameters (four actually, we will see exactly why later), two of them are optional (
ttlandgeneration_time). Both of them have a default value, however, in the case ofttlthe default value isn’t really used, it serves more as a marker to find out whether the argument was passed in or not.30.secondsis an extension that theActiveSupportlibrary adds to theIntegerclass. It doesn’t actually do anything, it just returnsself. 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 returnsself * 60andInteger#hoursand so on.) We will use this as an indication, that the type of the parameter should not beintbut ratherSystem.TimeSpan.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 sayfoo if a > b. So, the above is equivalent tounless ttl then return get(key) { yield } end.The next one is also easy:
unlessis just syntactic sugar forif not. So, we are now atif not ttl then return get(key) { yield } endThird 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 keywordnilis false, everything else is true. So, in this case the conditional will only be true, ifttlis eitherfalseornil.falseisn’t a terrible sensible value for a timespan, so the only interesting one isnil. The snippet would have been more clearly written like this:if ttl.nil? then return get(key) { yield } end. Since the default value for thettlparameter isnil, this conditional is true, if no argument was passed in forttl. 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.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 four arguments, 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, aFunc.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.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 ofString.Format(), which is exactly what we are going to translate it to.This is my feeble attempt at translating the Ruby version to C#:
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.