Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

using / IDisposable

using (var r = <IDisposable>) { … } · using var r = <IDisposable>;

C#'s `using`, exactly: `Dispose()` is called on every way out of the block — normal exit, return, break, continue, or an exception — last declared first. The resource must be an `IDisposable`, and the block may not hold a durable wait.

stable2 examples compiled by CIfunctionclassresources

Summary#

using is C#'s, and it means what C# says it means: the resource's Dispose() runs on every way out of the block — falling off the end, return, break, continue, or an exception on its way out — and a using var declaration lasts to the end of its enclosing block, with several of them disposed last declared first.

The platform owns the lifetime of everything it hands you — rows, HTTP calls, files — so none of those need a using. You reach for one when your class holds something outside Osy#: a session opened on an external database, a multipart upload started on a bucket, a lease taken on a remote lock. That class implements IDisposable with its own Dispose():

app Warehouse {
  model "model/**/*.osy";
  use Osysharp.Http;       // the manifest dependency that makes `Http.*` available
}

class WarehouseSession : IDisposable {
  public string Id = "";
  public bool Closed;

  public static WarehouseSession Open() {
    var r = Http.Post("https://warehouse.example.com/sessions", "{}", "application/json");
    return new WarehouseSession { Id = r.Body };
  }

  public void Dispose() {
    if (Closed) { return; }
    Http.Delete($"https://warehouse.example.com/sessions/{Id}");
    Closed = true;
  }
}

int CountRows(string table) {
  using (var session = WarehouseSession.Open()) {
    var r = Http.Get($"https://warehouse.example.com/sessions/{session.Id}/count/{table}");
    if (!r.IsSuccess) { return 0; }     // the session is closed here too
    return int.Parse(r.Body);
  }                                      // …and here
}

Signature#

using (var r = <expression>) <statement-or-block>          // the statement form
using (<Type> a = <expression>, b = <expression>) { … }      // several of one type — b is disposed before a
using (<expression>) { … }                                   // dispose a value you already hold
using var r = <expression>;                                  // the declaration form — to the end of the block

Description#

What it lowers to#

Exactly C#'s lowering, onto the try/finally the language already runs on the server and in the browser:

using (var r = Open()) { Work(r); }
// is
{ var r = Open(); try { Work(r); } finally { r?.Dispose(); } }

So the resource is created before the try — if creating it throws, there is nothing to dispose — and a null resource is skipped rather than dereferenced. Everything finally promises, using promises: see try / catch / finally.

The declaration form, and the order#

using var r = …; is scoped to the end of the block it is in. Two of them dispose in reverse order, because the second one is nested inside the first — the newest resource is released before the one it may depend on:

class Step : IDisposable {
  public string Name = "";
  public Trail Into = new Trail();
  public void Dispose() { Into.Log = Into.Log + Name; }
}

class Trail {
  public string Log = "";
  public void Run() {
    using var first = new Step { Name = ",1", Into = this };
    using var second = new Step { Name = ",2", Into = this };
    Log = "body";
  }                              // second is disposed, then first
}

string Order() {
  var trail = new Trail();
  trail.Run();
  return trail.Log;              // "body,2,1"
}

A using declaration needs a block to end in, so the body of a brace-less if cannot be one — C# refuses it too (CS1023). Write braces, or the statement form.

IDisposable is a real contract#

IDisposable is an ordinary interface with one member, void Dispose(), and a class takes it the way it takes any contract — class Lease : IDisposable { public void Dispose() { … } } (Interfaces — a contract several types can satisfy). using over a value that is not an IDisposable is refused, as C# refuses it (CS1674): there would be nothing to call.

class Note { public string Text = ""; }
using (var n = new Note()) { … }   // ✗ 'Note' is not IDisposable, so `using` has nothing to dispose (CS1674)

If there is genuinely nothing to release, there should be no using — keep the declaration and drop the word.

The one Osy# rule: no durable wait inside#

A body in Osy# can park: an awaited Workflow.Run, a saga step (await saga.Run(…), await saga.DisposeUnwind()), a join (await Workflow.WhenAll/WhenAny/When(…)), or a Workflow.Once(…, retry: …) can suspend the run and resume it later on another process — after a restart, a deploy, days later. A resource held across that gap is not one anybody can rely on, so a using scope may not contain one:

using (var lease = Lease.Take()) {
  await Workflow.Run("ship", order);   // ✗ a disposable can't be held across a durable wait;
}                                       //   close it before waiting, or open it again after

Close it before the wait, or open it again after. Ordinary calls inside a using — HTTP, reads, writes, other functions — are fine; only the waits that suspend the run are refused. As in .NET, a process that dies mid-block skips Dispose(): write Dispose() so the far side expires what it leaked (a session timeout, a lease's TTL).

Not the using at the top of a file#

using Osysharp.Http; at the top of a file is the namespace import it looks like — a different construct that happens to share the word. And await using var saga = Workflow.BeginSaga(); is the saga's compensation scope, not resource disposal; see the workflow docs.

See also#

Related

try / catch / finally

Handles a fault. C#'s syntax, including typed catches, catch filters and finally. The rows written inside a try block…

Interfaces — a contract several types can satisfy

An `interface` declares what a type must do without saying how — methods and property contracts, and no state. A member…

The C# Osy# leaves out — and why

Osy# is C#-shaped, and leaves out three things on purpose: unsafe code (pointers, fixed, stackalloc, function pointers…

Classes

A class is an in-memory shape — data plus the behaviour that belongs to it — and it never touches the database. That is…