Java7 の try-with-resources で複数のリソースを扱うときの書き方が、C# の using と全然違う

Java7 の try-with-resources と C# の using は、どちらも同じだと思っていました。
でも、複数のリソースを扱うときの書き方が全然違っていました。

// Java7, ひとつの try の中に複数の宣言を書く
try(BufferedReader reader = Files.newBufferedReader(path1, charset);
    BufferedWriter writer = Files.newBufferedWriter(path2, charset)){
    String line = reader.readLine();
    (以下省略)
// C#, 複数の using の中にひとつずつ宣言を書く
using (TextReader reader = File.OpenText(path1))
using (TextWriter writer = File.CreateText(path2)) {
    string line = reader.ReadLine();
    (以下省略)


この理由を調べてみたら、意外とおもしろいことがわかりました。


理由1:try/using のうしろが違う

Java7 の try-with-resources だと後ろに "{" がないとコンパイルできません

// Java7 (コンパイルNG:"{ が必要です。")
try(BufferedReader reader = Files.newBufferedReader(path, charset))
    reader.readLine();

しかし、C# の using だと "{" がなくてもコンパイルできます

// C# (コンパイルOK)
using (TextReader reader = File.OpenText(path))
    reader.ReadLine();


これは、それぞれの文法が以下のようになっているからです。


このため、try-with-resources を複数使うときはネストして書きます。

// Java7
try(BufferedReader reader = Files.newBufferedReader(path1, charset)){
    try(BufferedWriter writer = Files.newBufferedWriter(path2, charset)){
        String line = reader.readLine();
        (以下省略)

一方、using は淡々と並べて書きます。

// C#
using (TextReader reader = File.OpenText(path1))
using (TextWriter writer = File.CreateText(path2)) {
    string line = reader.ReadLine();
    (以下省略)

理由2:複数のリソースを記述するときの文法が違う

理由1を見ると、using の方がネストが深くならずにいい!と思われるかもしれません。
でも、try-with-resources は一つの文で複数のリソースが扱えるようになっているので、そちらを使えばネストが深くなりません。

// Java7, try-with-resources
try(BufferedReader reader1 = Files.newBufferedReader(path1, charset);
    BufferedReader reader2 = Files.newBufferedReader(path2, charset)){
    String line = reader1.readLine();
    (以下省略)


もちろん、C#でも同じようなことができます。

// C#, using
using (TextReader reader1 = File.OpenText(path1),
                  reader2 = File.OpenText(path2)) {
    string line = reader1.ReadLine();
    (以下省略)

しかし、大きな違いがあります。
try-with-resources は複数の宣言文が書けますが、using は一つの宣言文しか書けません*2


そのため、読み書きの両方をひとつの using で扱うことができません。

// C# (コンパイルNG:"for、using、fixed または declaration ステートメント
//     に 1 つ以上の型を使用することはできません。")
using (TextReader reader = File.OpenText(path1),
       TextWriter writer = File.CreateText(path2)) {
    string line = reader.ReadLine();
    (以下省略)


なので、using を複数使います。

// C#
using (TextReader reader = File.OpenText(path1))
using (TextWriter writer = File.CreateText(path2)) {
    string line = reader.ReadLine();
    (以下省略) 

結論

以前、Java7のAutoCloseable - 地平線に行くという記事を書いたときに「C# の using みたいです。」と書いたんですが、詳しく見てみると全然違いますね。
同じ Automatic Resource Management(ARM) を実現するための文法なのに、こんなに違うとは思っていませんでした。


おもしろいですね!

*1:ブロックはステートメントの一種にすぎないので、単一の文なら "{...}" は必要ありません。文法 → 18.1 Syntax - The Java Language Specification, Third Edition

*2:なので、単一の型しか使えません。これは、普通に型宣言をするときに、一つの文では「File file1, file2;」というように単一の型しか宣言できないのと同じです。複数の型を宣言したければ、2つの文に分けて「File file; Path path;」とするしかありませんが、これが using ではできません。