Where you’re challenged to do something useful with blocks.
Bill shares a little secret: “You know, a few years ago I was making a living out of writing C# code. I must admit that C# did have a few nice features. Let me show you one of those.”
Imagine that you’re writing a C# program that connects to a remote server and you have an object that represents the connection:
| RemoteConnection conn = new RemoteConnection("my_server"); |
| String stuff = conn.ReadStuff(); |
| conn.Dispose(); // close the connection to avoid a leak |
This code correctly disposes of the connection after using it. However, it doesn’t deal with exceptions. If ReadStuff throws an exception, then the last line is never executed, and conn is never disposed of. What the code should do is manage exceptions, disposing of the connection regardless of whether an exception is thrown. C# provides a keyword named using that goes through the whole process for you:
| RemoteConnection conn = new RemoteConnection("some_remote_server"); |
| using (conn) |
| { |
| conn.ReadData(); |
| DoMoreStuff(); |
| } |
The using keyword expects that conn has a method named Dispose. This method is called automatically after the code in the curly braces, regardless of whether an exception is thrown.
To refresh the basics of blocks, Bill throws a challenge at you: write a Ruby version of using. Make sure it passes this test:
| require 'test/unit' |
| require_relative 'using' |
| |
| class TestUsing < Test::Unit::TestCase |
| class Resource |
| def dispose |
| @disposed = true |
| end |
| |
| def disposed? |
| @disposed |
| end |
| end |
| |
| def test_disposes_of_resources |
| r = Resource.new |
| using(r) {} |
| assert r.disposed? |
| end |
| |
| def test_disposes_of_resources_in_case_of_exception |
| r = Resource.new |
| assert_raises(Exception) { |
| using(r) { |
| raise Exception |
| } |
| } |
| assert r.disposed? |
| end |
| end |
Take a look at this solution to the quiz:
| module Kernel |
| def using(resource) |
| begin |
| yield |
| ensure |
| resource.dispose |
| end |
| end |
| end |
You can’t define a new keyword, but you can fake it with a Kernel Method (). Kernel#using takes the managed resource as an argument. It also takes a block, which it executes. Regardless of whether the block completes normally, the ensure clause calls dispose on the resource to release it cleanly. There is no rescue clause, so any exception is still propagated to the code that calls Kernel#using.
Now that you’ve reviewed block basics, you can move to the second item on the list from : closures.