Object life-cycle management with using clauses

If you have an object with a complete life cycle in a particular extent of code, you can simplify your code with the using statement. The using statement is a more compact syntax and less error-prone way to work with resources than using try/catch/finally clauses. With using clauses:

  • Cleanup always occurs without requiring a separate finally clause.
  • You do not need to explicitly check whether variables for initialized resources have null values.
  • Code for locking and synchronizing resources is simplified.

For example, suppose you want to use an output stream. Typical code would open the stream, then use it, then close the stream to dispose of related resources. If something goes wrong while using the output stream, your code must close the output stream and perhaps check whether it successfully opened before closing it. In Gosu or standard Java, you could use a try/finally block like the following to clean up the stream:

OutputStream os = SetupMyOutputStream() // Insert your code that creates your output stream
try {
  // Do something with the output stream 
}
  finally {
  os.close();
}

You can simplify that code by using the Gosu using statement:

using ( var os = SetupMyOutputStream() ) {

// Do something with the output stream

} // Gosu disposes of the stream after it completes or if there is an exception

The basic form of a using clause is as follows:

using ( ASSIGNMENT_OR_LIST_OF_STATEMENTS ) {
  // Do something here
}

The parentheses after the using keyword can contain either a Gosu expression or a list of one or more Gosu statements delimited by commas, not semicolons.

Several categories of objects work with the using keyword: disposable objects, closeable objects, and reentrant objects. If you try to use an object that does not satisfy the requirements of one of these categories, Gosu displays a compiler error.

If Gosu detects that an object is in more than one category, at run time, Gosu considers the object to be in only one category. Gosu selects the category by the following precedence: disposable, closeable, reentrant. For example, if an object has a dispose and close method, Gosu only calls the dispose method.

Use the return statement to return values from using clauses.

If Gosu successfully evaluates and initializes all variables, each variable is examined in order. For each object, if the object has an enter action, that action is taken:

  • If the object implements the Lock interface, Gosu calls Lock.lock()
  • If the object implements the IReentrant interface, Gosu calls IRenntrant.enter()
  • If the object has a a method named lock and it takes no parameters, Gosu calls the lock method.
  • If the object was cast to the IMonitorLock interface, Gosu synchronizes on the variable

If the enter action completes without an exception, Gosu guarantees that the corresponding exit action for that object will run. If no enter action applies to the object, Gosu guarantees that the exit action for the object will run. Exit actions are called in the reverse order of declaration in the using clause. Exit actions are as follows:

  • If the object implements the IDisposable interface, Gosu calls IDisposable.dispose()
  • If the object has a method named dispose and it takes no parameters, Gosu calls the method.
  • If the object implements the Closeable interface, Gosu calls Closeable.close()
  • If the object has a method named close and it takes no parameters, Gosu calls the method.
  • If the object implements the IReentrant interface, Gosu calls IRenntrant.exit()
  • If the object implements the Lock interface, Gosu calls Lock.unlock()
  • If the object has a method named unlock that takes no parameters, Gosu calls the unlock method.
  • If the object was cast to the IMonitorLock interface, Gosu unsynchronizes on the variable

If an exception occurs during the execution of an exit action of an object, the exit actions defined before that exit action run, and then the exception throws. If an exception occurs in more than one exit action, the outermost exception, which occurs later in the code, takes precedence and is thrown to the surrounding code.