ちゃんとDisposeできてる? - use、using、そしてIDisposable

F#はMicrosoftの[Don Syme](https://twitter.com/dsyme "https://twitter.com/dsyme")氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と[位置付けられています](https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp "https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp")。実装はオープンソースの[MITライセンス](https://github.com/fsharp/fsharp/blob/master/License.txt "https://github.com/fsharp/fsharp/blob/master/License.txt")のもと、[githubのリポジトリ](https://github.com/fsharp/fsharp "https://github.com/fsharp/fsharp")で公開されています。

実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「[F# の概要](https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index "https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index")」など)。

なお、ここで対象とするF#のバージョンは4.1([FSharp.Core](https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core "https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core") 4.4.1.0)とします。

# Disposeできてる?
[Disposeメソッド](https://docs.microsoft.com/ja-jp/dotnet/api/system.idisposable.dispose?view=netframework-4.7.1#System_IDisposable_Dispose "https://docs.microsoft.com/ja-jp/dotnet/api/system.idisposable.dispose?view=netframework-4.7.1#System_IDisposable_Dispose")は、プログラムが占有(確保)していたリソース(メモリなど)を解放する処理を行います。これは[System.IDisposable](https://docs.microsoft.com/ja-jp/dotnet/api/system.idisposable?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.idisposable?view=netframework-4.7.1")インターフェースを実装したクラスに存在しています。リソースはファイルや通信などのI/O処理、スレッドの開始などで実行中のプログラムが一時的に占有します。それらを解放する処理を行うのが`Dispose`メソッドです。

リソースの解放を怠ると、プログラムがリソースを占有し続けることになります。さらに占有するリソースが膨張し続ければ実行環境に余分な負荷をかけることになりかねません。そこでF#には、`Dispose`メソッドを自動的に実行できる方法が提供されています。

それが[useとusing](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/resource-management-the-use-keyword "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/resource-management-the-use-keyword")です。これらを使うと`Dispose`メソッドを自動的に実行し、リソースを解放してくれます。２つの違いはリソースを解放するタイミングです。`use`ではブロックの処理が終わった後に同メソッドを実行するのに対して、`using`は引数に設定した関数の処理が終わると同メソッドが実行されます。

例えばテキストファイルを１行ずつ読み込んで処理を行う関数では、ファイルをオープンするとき、`use`はletの代わりとして使い、`using`は第1引数にリソース(ファイルのオープン)、第2引数にリソースを使う処理を表す関数を設定するような使い方ができます。

```fsharp:useによるリソースの確保と解放の例
(* テキストファイルを一行ずつ読み込んで処理を実行(use版) *)
let readLines file f =
  (* ファイルから一行読み込んで関数を実行 *)
  let rec readLine i (r: System.IO.StreamReader) =
    let line = r.ReadLine()  // ファイルから一行読み込む
    if line <> null
      then f i line  // 関数を実行
           readLine (i + 1) r  // 次の行を読み込む
      else ()
  (* 処理開始 *)
  use reader = System.IO.File.OpenText file  // ファイルオープン(ここからブロック終了まで有効)
  readLine 0 reader  // 実行後、自動的にファイルクローズ

readLines @"platforms.txt"
          (fun i line -> printfn "%i. %s" (i + 1) line)

(* platforms.txt *)
.NET Framework
.NET Core
Xamarin
ASP.NET
Azure

(* 結果 *)
1. .NET Framework
2. .NET Core
3. Xamarin
4. ASP.NET
5. Azure
```

```fsharp:usingによるリソースの確保と解放の例
(* テキストファイルを一行ずつ読み込んで処理を実行(using版) *)
let readLines file f =
  let rec readLine i (r: System.IO.StreamReader) =
    // ..... (use版に同じ)
  using (System.IO.File.OpenText file) (fun r -> readLine 0 r)  // rでオープンしたファイルを読み込む

readLines @"platforms.txt"
          (fun i line -> printfn "%i. %s" (i + 1) line)  // この関数実行後、自動的にファイルクローズ

// 結果省略
```

# 本当にDisposeできてる?
では、`use`と`using`によって本当に`Dispose`メソッドが実行されているのか確認してみます。メッセージを表示する`Dispose`メソッドを持つ`System.IDisposable`オブジェクトを定義し、それを`use`と`using`に設定し処理を実行するとどうなるでしょうか。`Dispose`メソッドが実行されるタイミングに注目です。

```fsharp:本当にDisposeメソッドが実行されるかを確認
(* Disposeメソッドが実行されるとメッセージを表示するSystem.IDisposableオブジェクト *)
let disposable s = { new System.IDisposable with
                       member this.Dispose() = printfn "%s" s }

(* useでDisposeメソッドが実行されるか? *)
let use_disposable() =
  use d = disposable "use_disposable end"  // Disposeメソッドが実行されるとメッセージ表示
  printfn "use_disposable start"
  printfn "use_disposable function end"

use_disposable()
(* 結果 *)
(*
use_disposable start
use_disposable function end
use_disposable end  (Disposeメソッド実行)
*)

(* usingでDisposeメソッドが実行されるか? *)
let using_disposable() =
  using (disposable "using_disposable end")  // Disposeメソッドが実行されるとメッセージ表示
    (fun d -> printfn "using_disposable start")
  printfn "using_disposable function end"

using_disposable()
(* 結果 *)
(*
using_disposable start
using_disposable end  (Disposeメソッド実行)
using_disposable function end
*)
```

`Dispose`メソッドが実行されるタイミングが、`use`ではブロック(インデントの深さが同じ部分)の最後、`using`では第2引数(関数)の実行後であることがわかります。


# これはDisposeできる?
これでuseとusingによってDisposeメソッドを実行できるのは確認できました。また、それぞれでDisposeメソッドが実行されるタイミングが違うこともわかりました。では、どのクラス、インターフェース、オブジェクトにDisposeメソッドが実装されているのでしょうか。

[System.IDisposable](https://docs.microsoft.com/ja-jp/dotnet/api/system.idisposable?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.idisposable?view=netframework-4.7.1")インターフェースのドキュメントには、それを実装したクラスなどの一覧があり、これらを継承したクラスなども順にドキュメントを辿って参照していけます。逆に「このクラスはSystem.IDisposableインターフェースを実装しているのか」もAPIのドキュメント冒頭にあるシグネチャを見ればわかります。

ですが、APIのドキュメントにアクセスできないときはどうしたら良いでしょうか。いくつか方法が考えられます。これら以外にも良い方法があるかもしれません。

## letでチェック
あるオブジェクトで`System.IDisposable`が実装されているかを、仮引数の型を指定した`let`でチェックしてみます。実引数の型が`System.IDisposable`を実装していなければエラーとなります。

```fsharp:System.IDisposableが実装されているかをletでチェック
let checkIDisposable (x: System.IDisposable) =
  try
    x.GetType().FullName
    |> printfn "%sはSystem.IDisposableを実装しています"
  with
    ex -> eprintfn "%s" ex.Message

checkIDisposable (new System.IO.MemoryStream())
// 結果: System.IO.MemoryStreamはSystem.IDisposableを実装しています

checkIDisposable (new System.Timers.Timer())
// 結果: System.Timers.TimerはSystem.IDisposableを実装しています

checkIDisposable System.Drawing.SystemFonts.DefaultFont
// 結果: System.Drawing.FontはSystem.IDisposableを実装しています

checkIDisposable <| obj()
// 結果: 型 'System.Object' は型 'System.IDisposable' と互換性がありません (エラー)
```

## matchと(:?)でチェック
[match式](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/match-expressions "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/match-expressions")と`:?`で表される[型テスト演算子](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/symbol-and-operator-reference/ "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/symbol-and-operator-reference/")でチェックしてみます。


```fsharp:System.IDisposeを実装しているかをmatchと:?でチェック
let checkIDisposable (x: obj) =
  printfn "%sはSystem.IDisposableを実装していま%s"
  <| x.GetType().FullName
  <| match x with
     | :? System.IDisposable -> "す"  // ここでチェック!
     | _ -> "せん"

checkIDisposable (new System.IO.MemoryStream())
// 結果: System.IO.MemoryStreamはSystem.IDisposableを実装しています

checkIDisposable (new System.Net.HttpListener())
// 結果: System.Net.HttpListenerはSystem.IDisposableを実装しています

checkIDisposable <| obj()
// 結果: System.ObjectはSystem.IDisposableを実装していません
```

## キャストでチェック
`System.IDisposable`にキャストできるかどうかでもチェックできます。

```fsharp:System.IDisposableにキャストできるかでチェック
let checkIDisposable<'T>(x: obj) =
  try
    x :?> 'T |> ignore  // ここでチェック!
    printfn "%sは%sを実装しています"
    <| x.GetType().FullName
    <| typeof<'T>.FullName
  with
    ex -> eprintfn "%s" ex.Message

checkIDisposable<System.IDisposable> <| new System.IO.MemoryStream()
// 結果: System.IO.MemoryStreamはSystem.IDisposableを実装しています
checkIDisposable<System.IDisposable> <| obj()
// 結果: 型 'System.Object' のオブジェクトを型 'System.IDisposable' にキャストできません 。
```

## ジェネリックでチェック
ジェネリックの型変数に対する`when`によるチェックもできます。こちらはオブジェクトではなく型名による方法です。型変数に与えられる型が`System.IDisposable`を実装していなければエラーとなります。

```fsharp:ジェネリックの型変数に対してSystem.IDisposableをチェック
let checkIDisposable<'T when 'T :> System.IDisposable> =
  typeof<'T>.FullName
  |> printfn "%sはSystem.IDisposableを実装しています"

checkIDisposable<System.IO.StreamReader>
// 結果: System.IO.StreamReaderはSystem.IDisposableを実装しています

checkIDisposable<System.Data.Common.DbConnection>
// 結果: System.Data.Common.DbConnectionはSystem.IDisposableを実装しています

checkIDisposable<System.Object>
// 結果: 型 'System.Object' は型 'System.IDisposable' と互換性がありません (エラー)
```

## typeofでチェック
`typeof<'T>`で`GetInterfaces`メソッドを実行すると実装されているインターフェース(`System.Type`)の一覧を得られます。その中に`System.IDisposable`が含まれているかをチェックすれば、`'T`が`System.IDisposable`を実装していることを確認できます。

```fsharp:typeof<'T>でSystem.IDisposableが実装された型かをチェック
let checkIDisposable<'T> =
  let t = typeof<'T>
  printfn "%sはSystem.IDisposableを実装していま%s"
  <| t.FullName
  <| if 
       t.GetInterfaces()
       |> Array.exists (fun x -> x = typeof<System.IDisposable>)  // ここでチェック
     then "す"
     else "せん"

checkIDisposable<System.IO.StreamReader>
// 結果: System.IO.StreamReaderはSystem.IDisposableを実装しています

checkIDisposable<System.Threading.Tasks.Task>
// 結果: System.Threading.Tasks.TaskはSystem.IDisposableを実装しています

checkIDisposable<System.Object>
// 結果: System.ObjectはSystem.IDisposableを実装していません
```

# 間接的なIDisposableの継承
`System.IDisposable`の継承(および実装)はクラスに対して直接行われるものだけでなく、間接的に行われるものもあります。

[System.Collections.Generic.IEnumerable<'T>](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.ienumerable-1?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.ienumerable-1?view=netframework-4.7.1")インターフェースは`System.IDisposable`を継承していないのですが、これが持つ[GetEnumerator](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.ienumerable-1.getenumerator?view=netframework-4.7.1#System_Collections_Generic_IEnumerable_1_GetEnumerator "https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.ienumerable-1.getenumerator?view=netframework-4.7.1#System_Collections_Generic_IEnumerable_1_GetEnumerator")メソッドは、[System.Collections.Generic.IEnumerator<'T>](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.ienumerator-1?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.ienumerator-1?view=netframework-4.7.1")インターフェースの実装を戻り値とします。この`IEnumerator`が`System.IDisposable`を継承しているのです。そのため`IEnumerable`を実装するものは内部で`IEnumerator`と`IDisposable`を実装している可能性があります。

`IEnumerable<'T>`はF#の`seq<'T>`や`for`に対応していますので、これが実装されたクラスなどを知っておくと、さまざまなところで役に立ちます。

たとえば[System.String](https://docs.microsoft.com/ja-jp/dotnet/api/system.string?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.string?view=netframework-4.7.1")(文字列:`string`)には`System.Collections.Generic.IEnumerable<char>`が実装されていて、`for`で1文字ずつ抽出しながらの繰り返し処理を実行できます。

```fsharp:文字列から1文字ずつ抽出して繰り返し処理
for c in ".NET" do printfn "%A 0x%x" c (uint32 c)

(* 結果 *)
'.' 0x2e
'N' 0x4e
'E' 0x45
'T' 0x54
```

これができるなら、文字列から1行ずつ抽出して繰り返し処理できるようにしたいと思い、そのためのプロパティ`AllLines`を拡張してみました。型は`seq<string>`です。内部で[System.IO.StringReader](https://docs.microsoft.com/dotnet/api/system.io.stringreader?view=netframework-4.7.1 "https://docs.microsoft.com/dotnet/api/system.io.stringreader?view=netframework-4.7.1")を使っていますが、それを`IEnumerator`の`Dispose`メソッドで解放するため、`use`は使っていません。

```fsharp:Stringを拡張したAllLinesプロパティの使用例
// 改行込みの文字列
let platforms = ".NET Framework
.NET Core
Xamarin
ASP.NET
Azure"

// 1行ごとの繰り返し処理
platforms.AllLines
|> Seq.iteri (fun n line -> printfn "%i. %s" (n + 1) line)

(* 結果 *)
1. .NET Framework
2. .NET Core
3. Xamarin
4. ASP.NET
5. Azure
```

```fsharp:文字列から1行ずつ抽出して繰り返し処理をできるようにするAllLinesプロパティの拡張(型はseq<string>)
type System.String with
  member this.AllLines  // プロパティの拡張
    with get() =
      if this = null
        then seq<string> []
        else
          let self = this
          let mutable reader = new System.IO.StringReader(self)  // IEnumeratorのDisposeで解放するのでuseではない
          let mutable line = null
          (* プロパティの値となるIEnumerableオブジェクトの実装 *)
          {new System.Collections.Generic.IEnumerable<string> with
             member this.GetEnumerator(): System.Collections.IEnumerator =
               this.GetEnumerator() :> System.Collections.IEnumerator
             member this.GetEnumerator() =
               (* 繰り返し処理を行うIEnumeratorオブジェクトの実装 *)
               {new System.Collections.Generic.IEnumerator<string> with
                 member this.Current with get(): obj    = line |> box
                 member this.Current with get(): string = line
                 member this.MoveNext() =
                   line <- reader.ReadLine()
                   line <> null
                 member this.Dispose() =
                   reader.Dispose()  // StringReaderのクローズ(リソースの解放)
                   // printfn "String.AllLines Disposed!!"
                 member this.Reset() =
                   this.Dispose()
                   reader <- new System.IO.StringReader(self)
                   line   <- null
               }
          }
```

# 実はuseもusingもいらなかった...
.NET Frameworkのメソッドはとても充実していますので、実際には`use`も`using`もいらなかった...ということもあります。

冒頭に掲げた1行ずつの処理は[File.ReadAllLines](https://docs.microsoft.com/ja-jp/dotnet/api/system.io.file.readalllines?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.io.file.readalllines?view=netframework-4.7.1")メソッドを使えば`use`も`using`も改めて書かなくて済みます(ただしファイルとメモリの容量には注意)。余分な労力をかけないためにも、どんなメソッドがあるかをきちんと調べておいたほうがよさそうです。

```fsharp:実はuseもusingもいらなかった...
System.IO.File.ReadAllLines(@"platforms.txt")
|> Array.iteri (fun i line -> printfn "%i %s" (i + 1) line)

(* platforms.txt *)
.NET Framework
.NET Core
Xamarin
ASP.NET
Azure

(* 結果 *)
1. .NET Framework
2. .NET Core
3. Xamarin
4. ASP.NET
5. Azure
```

---
ここをご覧いただいた方々の参考になればと思います。