2010-10-15T20:28:17の更新内容

programming/netfx2/overview/clone_dispose/index.wiki.txt

current previous
1,2099 1,702
~
${smdncms:title,オブジェクトの複製と破棄(ICloneable, IDisposable)}
${smdncms:title,オブジェクトの複製・破棄(IClonable, IDisposable)}
+
${smdncms:keywords,ICloneable,Clone,MemberwiseClone,コピー,複製,IDisposable,破棄,解放}
 
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
${smdncms:meta,toc-amazonlivelink-keyword,books-jp,.net framework}
-
#googleadunit
 

        

        
~
*オブジェクトの複製
*インスタンスのコピー
~
ここではオブジェクトの複製を作成する方法と、ICloneableインターフェイスについて解説します。
**値型と参照型における代入の違い
-
まずはじめにIClonableインターフェイスを用いたオブジェクトの複製方法について検証してみようと思うのですが、その前に値型と参照型の代入における動作の違いについて軽くおさらいしておきたいと思います。
 

        

        
~
**代入とコピー
#code(vb,値型と参照型の違い){{
~
もっとも簡単にオブジェクトのコピーを作成する方法として、単純に代入する方法があります。
Module ClonableAndDisposable
 

        

        
~
#tabpage(C#)
    ' 参照型
~
#code(cs){{
    Class ReferencialTypeMusume
+
using System;
+

          
+
struct ValueType
+
{
+
  public int ID;
+
  public string Name;
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    ValueType v1, v1;
+

          
+
    v1.ID = 2;
+
    v1.Name = "Alice";
+

          
+
    Console.WriteLine("{0}:{1}", v1.ID, v1.Name);
+

          
+
    v2 = v1; // 代入によるコピー
+

          
+
    Console.WriteLine("{0}:{1}", v2.ID, v2.Name);
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Structure ValueType
        Public Name As String
+
  Public ID As Integer
+
  Public Name As String
+
End Structure
 

        

        
~
Class Sample
    End Class
+
  Shared Sub Main()
+
    Dim v1, v2 As ValueType
 

        

        
~
    v1.ID = 2
    ' 値型
~
    v1.Name = "Alice"
    Structure ValueTypeMusume
 

        

        
~
    Console.WriteLine("{0}:{1}", v1.ID, v1.Name)
        Public Name As String
 

        

        
~
    v2 = v1 ' 代入によるコピー
    End Structure
 

        

        
~
    Console.WriteLine("{0}:{1}", v2.ID, v2.Name)
    ' アプリケーションのエントリーポイント
~
  End Sub
    Sub Main()
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        ' 参照型の場合
~
2:Alice
        Dim r1 As New ReferencialTypeMusume(), r2 As New ReferencialTypeMusume()
+
2:Alice
+
}}
 

        

        
~
代入によるコピーは値型のみで行えます。 値型では、代入により代入元のすべてのメンバが代入先にコピーされますが、参照型では代入元のインスタンスへの参照が代入されます。 このため、代入元のインスタンスのメンバに対する変更は、代入先にも影響します。
        r1.Name = "加護亜依"
-
        r2.Name = "石川梨華"
 

        

        
~
#tabpage(C#)
        Console.WriteLine("r1.Name: {0}, r2.Name: {1}", r1.Name, r2.Name)
+
#code(cs){{
+
using System;
 

        

        
~
// 値型
        ' 「参照」を代入
~
struct ValueType
        r1 = r2
+
{
+
  public int ID;
+
  public string Name;
+
}
 

        

        
~
// 参照型
        r2.Name = "高橋愛"
+
class ReferenceType
+
{
+
  public int ID;
+
  public string Name;
+
}
 

        

        
~
class Sample
        Console.WriteLine("r1.Name: {0}, r2.Name: {1}", r1.Name, r2.Name)
+
{
+
  static void Main()
+
  {
+
    ValueType v1, v2;
 

        

        
+
    v1.ID = 2;
+
    v1.Name = "Alice";
 

        

        
~
    Console.WriteLine("v1 {0}:{1}", v1.ID, v1.Name);
        ' 単なる改行
-
        Console.WriteLine()
 

        

        
+
    v2 = v1; // 代入による値のコピー
 

        

        
~
    Console.WriteLine("v2 {0}:{1}", v2.ID, v2.Name);
        ' 値型の場合
-
        Dim v1 As New ValueTypeMusume(), v2 As New ValueTypeMusume()
 

        

        
~
    v1.Name = "Bob"; // 代入元のメンバを変更
        v1.Name = "加護亜依"
-
        v2.Name = "石川梨華"
 

        

        
~
    Console.WriteLine("v2 {0}:{1}", v2.ID, v2.Name);
        Console.WriteLine("v1.Name: {0}, v2.Name: {1}", v1.Name, v2.Name)
+
    Console.WriteLine();
 

        

        
~
    ReferenceType r1, r2;
        ' 「値」を代入
-
        v1 = v2
 

        

        
~
    r1 = new ReferenceType();
        v2.Name = "高橋愛"
+
    r1.ID = 2;
+
    r1.Name = "Alice";
 

        

        
~
    Console.WriteLine("r1 {0}:{1}", r1.ID, r1.Name);
        Console.WriteLine("v1.Name: {0}, v2.Name: {1}", v1.Name, v2.Name)
 

        

        
~
    r2 = r1; // 代入による参照のコピー
    End Sub
 

        

        
~
    Console.WriteLine("r2 {0}:{1}", r2.ID, r2.Name);
End Module
-
}}
 

        

        
~
    r1.ID = 3; // 代入元のメンバを変更
#prompt(実行結果){{
~
    r1.Name = "Bob";
r1.Name: 加護亜依, r2.Name: 石川梨華
-
r1.Name: 高橋愛, r2.Name: 高橋愛
 

        

        
~
    Console.WriteLine("r2 {0}:{1}", r2.ID, r2.Name);
r1.Name: 加護亜依, r2.Name: 石川梨華
~
  }
r1.Name: 石川梨華, r2.Name: 高橋愛
~
}
Press any key to continue
 
}}
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
' 値型
値型と参照型の根本的な違いについてをここで述べることはしませんが、代入だけについてみれば、値型ではその値すべてを代入するのに対して、参照型はその参照のみを代入します。
+
Structure ValueType
+
  Public ID As Integer
+
  Public Name As String
+
End Structure
+

          
+
' 参照型
+
Class ReferenceType
+
  Public ID As Integer
+
  Public Name As String
+
End Class
 

        

        
~
Class Sample
**参照型インスタンスのコピー
~
  Shared Sub Main()
それでは参照型インスタンスの参照の代入ではなく、インスタンスそのもののコピーを生成するにはどのような方法があるかを検討してみたいと思います。 まず、その最も簡単な方法はすべてのフィールドを直接コピーするという方法です。
+
    Dim v1, v2 As ValueType
 

        

        
~
    v1.ID = 2
#code(vb){{
~
    v1.Name = "Alice"
Module ClonableAndDisposable
 

        

        
~
    Console.WriteLine("v1 {0}:{1}", v1.ID, v1.Name)
    ' 参照型
-
    Class ReferencialTypeMusume
 

        

        
~
    v2 = v1 ' 代入による値のコピー
        Public Name As String
-
        Public Age As Integer
-
        Public BloodType As String
 

        

        
~
    Console.WriteLine("v2 {0}:{1}", v2.ID, v2.Name)
    End Class
 

        

        
~
    v1.Name = "Bob" ' 代入元のメンバを変更
    ' アプリケーションのエントリーポイント
-
    Sub Main()
 

        

        
~
    Console.WriteLine("v2 {0}:{1}", v2.ID, v2.Name)
        ' インスタンスを生成
~
    Console.WriteLine()
        Dim r1 As New ReferencialTypeMusume(), r2 As New ReferencialTypeMusume()
 

        

        
~
    Dim r1, r2 As ReferenceType
        ' インスタンスを初期化
-
        With r1
 

        

        
~
    r1 = New ReferenceType()
            .Name = "加護亜依"
~
    r1.ID = 2
            .Age = 15
~
    r1.Name = "Alice"
            .BloodType = "AB"
 

        

        
~
    Console.WriteLine("r1 {0}:{1}", r1.ID, r1.Name)
        End With
 

        

        
~
    r2 = r1 ' 代入による参照のコピー
        ' インスタンスのフィールドをコピー
-
        With r2
 

        

        
~
    Console.WriteLine("r2 {0}:{1}", r2.ID, r2.Name)
            .Name = r1.Name
-
            .Age = r1.Age
-
            .BloodType = r1.BloodType
 

        

        
~
    r1.ID = 3 ' 代入元のメンバを変更
        End With
+
    r1.Name = "Bob"
 

        

        
~
    Console.WriteLine("r2 {0}:{1}", r2.ID, r2.Name)
    End Sub
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
End Module
+
v1 2:Alice
+
v2 2:Alice
+
v2 2:Alice
+

          
+
r1 2:Alice
+
r2 2:Alice
+
r2 3:Bob
 
}}
}}
 

        

        
~
**コピーコンストラクタ
これでもコピーは行えますが、重大な問題が二つあります。 このようなフィールドの少ないクラスであれば問題ないのですが、たくさんのフィールドを持つクラスや、少ないフィールドしか持たないクラスでも何度もコピーすることが必要な場合などには、コピーの度に大量のソースコードを書かなければならないと言う問題がその一つです。 また、この方法ではパブリックなフィールドしかコピーできません。 プライベートなフィールドについてはコピーする手段を持ち得ません。 そこで、これらの問題を解決する方法としてあげられるコピー方法がコンストラクタを用いる方法です。
+
値型・参照型のどちらでも使える方法として、型にコピーコンストラクタを用意する方法があります。 この方法には、参照型でも適用できるほか、必要に応じてコピーの際の動作も定義できるという利点もあります。
 

        

        
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+

          
+
class Account
+
{
+
  public int ID;
+
  public string Name;
+

          
+
  // デフォルトコンストラクタ
+
  public Account()
+
  {
+
  }
+

          
+
  // コピーコンストラクタ
+
  public Account(Account source)
+
  {
+
    this.ID   = source.ID;
+
    this.Name = source.Name;
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Account a1, a2;
+

          
+
    a1 = new Account();
+
    a1.ID = 2;
+
    a1.Name = "Alice";
+

          
+
    Console.WriteLine("a1 {0}:{1}", a1.ID, a1.Name);
+

          
+
    a2 = new Account(a1); // コピーコンストラクタを使ってコピー
+

          
+
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name);
+

          
+
    a1.ID = 3; // コピー元のメンバを変更
+
    a1.Name = "Bob";
+

          
+
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name);
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
~
Imports System
Module ClonableAndDisposable
 

        

        
~
Class Account
    ' 参照型
~
  Public ID As Integer
    Class ReferencialTypeMusume
+
  Public Name As String
+

          
+
  ' デフォルトコンストラクタ
+
  Public Sub New()
+
  End Sub
+

          
+
  ' コピーコンストラクタ
+
  Public Sub New(ByVal source As Account)
+
    MyClass.ID   = source.ID
+
    MyClass.Name = source.Name
+
  End Sub
+
End Class
 

        

        
~
Class Sample
        ' 様々なスコープを持つフィールド
~
  Shared Sub Main()
        Public Name As String
~
    Dim a1, a2 As Account
        Protected Age As Integer
-
        Private BloodType As String
 

        

        
~
    a1 = New Account()
        ' コンストラクタ (初期化用)
~
    a1.ID = 2
        Public Sub New(ByVal name As String, ByVal age As Integer, ByVal bloodType As String)
+
    a1.Name = "Alice"
 

        

        
~
    Console.WriteLine("a1 {0}:{1}", a1.ID, a1.Name)
            Me.Name = name
-
            Me.Age = age
-
            Me.BloodType = bloodType
 

        

        
~
    a2 = New Account(a1) ' コピーコンストラクタを使ってコピー
        End Sub
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name)
        ' コンストラクタ (コピー用)
-
        Public Sub New(ByVal musume As ReferencialTypeMusume)
 

        

        
~
    a1.ID = 3 ' コピー元のメンバを変更
            Me.Name = musume.Name
~
    a1.Name = "Bob"
            Me.Age = musume.Age
-
            Me.BloodType = musume.BloodType
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name)
        End Sub
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        ' インスタンスの状態を表示するためのメソッド
~
r1 2:Alice
        Public Overrides Function ToString() As String
+
r2 2:Alice
+
r2 2:Alice
+
}}
 

        

        
~
当然、コピー元とコピー先のインスタンスは異なるものなので、コピー元に変更を加えてもコピー先には影響しません。 ただ、この方法ではコピーすべきメンバが多数ある型の場合は、コピーコンストラクタの記述が面倒になるという欠点もあります。
            Return Me.Name + " " + Me.Age.ToString() + "歳, " + Me.BloodType + "型"
 

        

        
~
**Object.MemberwiseClone
        End Function
+
Objectクラスより継承される&msdn(netfx,method,System.Object.MemberwiseClone){MemberwiseCloneメソッド};を用いることで、コピーすべきメンバが多数ある場合でも簡単にオブジェクトの複製を作成することが出来ます。 このメソッドはオブジェクトの[[簡易コピー>#ShallowCopyAndDeepCopy]]を作成します。 パブリックではなくプロテクトなメソッドのため、外部から直接呼び出すことは出来ません。 また、このメソッドの戻り値はObject型なので必要に応じてキャストする必要があります。
 

        

        
~
#tabpage(C#)
    End Class
+
#code(cs){{
+
using System;
 

        

        
~
class Account
    ' アプリケーションのエントリーポイント
~
{
    Sub Main()
+
  public int ID;
+
  public string Name;
 

        

        
~
  // コピーを作成するメソッド
        ' 変数を宣言
~
  public Account Clone()
        Dim r1, r2 As ReferencialTypeMusume
+
  {
+
    return (Account)MemberwiseClone();
+
  }
+
}
 

        

        
~
class Sample
        ' インスタンスを生成
~
{
        r1 = New ReferencialTypeMusume("加護亜依", 15, "AB")
+
  static void Main()
+
  {
+
    Account a1, a2;
 

        

        
~
    a1 = new Account();
        ' インスタンスのコピーを生成
~
    a1.ID = 2;
        r2 = New ReferencialTypeMusume(r1)
+
    a1.Name = "Alice";
 

        

        
~
    Console.WriteLine("a1 {0}:{1}", a1.ID, a1.Name);
        ' 状態を表示
-
        Console.WriteLine("r1: {0}", r1)
-
        Console.WriteLine("r2: {0}", r2)
 

        

        
~
    a2 = a1.Clone(); // MemberwiseCloneを使ってコピー
    End Sub
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name);
End Module
-
}}
 

        

        
~
    a1.ID = 3; // コピー元のメンバを変更
#prompt(実行結果){{
~
    a1.Name = "Bob";
r1: 加護亜依 15歳, AB型
~

          
r2: 加護亜依 15歳, AB型
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name);
Press any key to continue
+
  }
+
}
 
}}
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Account
これで様々なスコープのフィールドを持ったクラスのインスタンスでも、比較的簡単な方法ですべてのフィールドをコピーしてインスタンスのコピーを作ることができるようになります。 ただ、このような方法を用いてもフィールドが多数ある場合はコンストラクタにコピーのコードを記述するだけでも一苦労です。
+
  Public ID As Integer
+
  Public Name As String
+

          
+
  ' コピーを作成するメソッド
+
  Public Function Clone() As Account
+
    Return CType(MemberwiseClone(), Account)
+
  End Function
+
End Class
 

        

        
~
Class Sample
*IClonableインターフェイスとMemberwiseClone()メソッド
~
  Shared Sub Main()
これまで検討した方法よりももっと簡単にコピーを生成する方法にIClonableインターフェイスとMemberwiseClone()メソッドを利用する方法があります。 IClonableインターフェイスはObject型を戻り値とするCloneメソッドだけを持ちます。 このインターフェイスを実装することでそのクラスはインスタンスのコピーを生成することができると言うことを示すことができます。
+
    Dim a1, a2 As Account
 

        

        
~
    a1 = New Account()
また、MemberwiseClone()メソッドはObjectクラスより継承されるメソッドなので、すべてのクラスがこのメソッドを暗黙的に継承しています。 このメソッドはProtectedで、インスタンスの簡易コピーを生成します。 参考までに、このメソッドは派生クラスでオーバーライドすることはできません。 簡易コピーとは何かを説明する前に、実際にこのインターフェイスとメソッドを利用したコピー方法の例を見てみます。
+
    a1.ID = 2
+
    a1.Name = "Alice"
 

        

        
~
    Console.WriteLine("a1 {0}:{1}", a1.ID, a1.Name)
#code(vb){{
-
Option Strict On
 

        

        
~
    a2 = a1.Clone() ' MemberwiseCloneを使ってコピー
Module ClonableAndDisposable
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name)
    ' 参照型
-
    Class Musume
 

        

        
~
    a1.ID = 3 ' コピー元のメンバを変更
        ' IClonableインターフェイスを実装
~
    a1.Name = "Bob"
        Implements ICloneable
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name)
        ' 様々なスコープを持つフィールド
~
  End Sub
        Public Name As String
~
End Class
        Protected Age As Integer
~
}}
        Private BloodType As String
+
#tabpage-end
 

        

        
~
#prompt{{
        ' コンストラクタ (初期化用)
~
r1 2:Alice
        Public Sub New(ByVal name As String, ByVal age As Integer, ByVal bloodType As String)
+
r2 2:Alice
+
r2 2:Alice
+
}}
 

        

        
~
MemberwiseCloneメソッドが返すオブジェクトはコピー元とは異なるインスタンスなので、コピー元に変更を加えてもコピー先には影響しません。 なお、このメソッドは派生クラスでオーバーライドすることはできないため、複製時の動作をカスタマイズすることは出来ません。 また当然ながら、コピーされるのはインスタンスメンバのみです。
            Me.Name = name
-
            Me.Age = age
-
            Me.BloodType = bloodType
 

        

        
~
***&aname(ShallowCopyAndDeepCopy){簡易コピーと詳細コピー};
        End Sub
+
MemberwiseCloneメソッドはオブジェクトの簡易コピーを作成します。 コピーの種類には簡易コピーの他に詳細コピーというコピー方法があり、両者の違いは次のようになります。
 

        

        
~
|*簡易コピーと詳細コピーの動作
        ' Clone()メソッドの実装
~
|~|~簡易コピー&br;(浅いコピー、shallow copy)|~詳細コピー&br;(深いコピー、deep copy)|h
        Public Function Clone() As Object Implements ICloneable.Clone
+
|~値型のフィールド|ビット単位でのコピー|ビット単位でのコピー|
+
|~参照型のフィールド|参照のみのコピー&br;(同一のインスタンスを参照)|参照先のインスタンスをコピー&br;(インスタンスの複製を作成)|
 

        

        
~
このように、MemberwiseCloneメソッドでは参照型のフィールドは参照のみがコピーされるようになります。 この違いを明確に示す例を挙げてみます。 以下の例において、コピーを行うクラスには配列、つまり参照型のフィールドが含まれています。
            ' MemberwiseClone()メソッドを利用して簡易コピーを生成
-
            Return Me.MemberwiseClone()
 

        

        
~
#tabpage(C#)
        End Function
+
#code(cs){{
+
using System;
 

        

        
~
class Account
        ' インスタンスの状態を表示するためのメソッド
~
{
        Public Overrides Function ToString() As String
+
  public int ID;
+
  public string Name;
+
  public string[] ContactAddresses;
 

        

        
~
  public Account Clone()
            Return Me.Name + " " + Me.Age.ToString() + "歳, " + Me.BloodType + "型"
+
  {
+
    return (Account)MemberwiseClone();
+
  }
+
}
 

        

        
~
class Sample
        End Function
+
{
+
  static void Main()
+
  {
+
    Account a1, a2;
 

        

        
~
    a1 = new Account();
    End Class
+
    a1.ID = 2;
+
    a1.Name = "Alice";
+
    a1.ContactAddresses = new string[] {"alice@example.com", "http://example.net/~alice/"};
 

        

        
~
    Console.WriteLine("a1 {0}:{1} ({2})", a1.ID, a1.Name, string.Join(", ", a1.ContactAddresses));
    ' アプリケーションのエントリーポイント
-
    Sub Main()
 

        

        
~
    a2 = a1.Clone(); // MemberwiseCloneを使ってコピー
        ' 変数を宣言
-
        Dim r1, r2 As Musume
 

        

        
~
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses));
        ' インスタンスを生成
-
        r1 = New Musume("加護亜依", 15, "AB")
 

        

        
~
    a1.ContactAddresses[0] = "bob@example.com"; // コピー元の参照型フィールドに変更を加える
        ' インスタンスの簡易コピーを生成
-
        r2 = DirectCast(r1.Clone(), Musume)
 

        

        
~
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses));
        ' 状態を表示
~
  }
        Console.WriteLine("r1: {0}", r1)
~
}
        Console.WriteLine("r2: {0}", r2)
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Account
    End Sub
+
  Public ID As Integer
+
  Public Name As String
+
  Public ContactAddresses() As String
+

          
+
  Public Function Clone() As Account
+
    Return CType(MemberwiseClone(), Account)
+
  End Function
+
End Class
 

        

        
~
Class Sample
End Module
~
  Shared Sub Main()
}}
+
    Dim a1, a2 As Account
 

        

        
~
    a1 = New Account()
#prompt(実行結果){{
~
    a1.ID = 2
r1: 加護亜依 15歳, AB型
~
    a1.Name = "Alice"
r2: 加護亜依 15歳, AB型
~
    a1.ContactAddresses = New String() {"alice@example.com", "http://example.net/~alice/"}
Press any key to continue
+

          
+
    Console.WriteLine("a1 {0}:{1} ({2})", a1.ID, a1.Name, string.Join(", ", a1.ContactAddresses))
+

          
+
    a2 = a1.Clone() ' MemberwiseCloneを使ってコピー
+

          
+
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses))
+

          
+
    a1.ContactAddresses(0) = "bob@example.com" ' コピー元の参照型フィールドに変更を加える
+

          
+
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses))
+
  End Sub
+
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
#prompt{{
この実行結果を見てわかるとおり、コピーに関するコードを記述することなくインスタンスのコピーを生成できていることがわかると思います。 ただ、この例ではコピーするためのメソッドを用意すればよいだけなのでIClonableインターフェイスは必ずしも実装する必要はありませんが、IClonable インターフェイスを実装することによってこのクラスがインスタンスのコピーを生成する能力をもつということを明示的にすることはできます。
+
a1 2:Alice (alice@example.com, http://example.net/~alice/)
+
a2 2:Alice (alice@example.com, http://example.net/~alice/)
+
a2 2:Alice (bob@example.com, http://example.net/~alice/)
+
}}
 

        

        
~
このように、MemberwiseCloneメソッドによって簡易コピーされたa2.ContactAddressesフィールドはa1.ContactAddressesフィールドと同じ配列を参照するため、コピー元に変更を加えるとコピー先にも影響します。 MemberwiseCloneメソッドで詳細コピーを行うことは出来ないため、簡易コピーを作成してから必要に応じて参照型フィールドの複製を行う必要があります。 以下の例は、先の例を詳細コピーを行うように書き換えたものです。
また、IClonableインターフェイスでは汎用性を高めるためにIClonable.Clone()メソッドの戻り値は Object型になっていますが、このままだとこのメソッドを呼び出すたびに52行目のようにキャストが必要になります(Option Strict が On の場合)。 そこで、この実装を隠蔽して次のように書き換えるのがよいと思われます。
 

        

        
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+

          
+
class Account
+
{
+
  public int ID;
+
  public string Name;
+
  public string[] ContactAddresses;
+

          
+
  public Account Clone()
+
  {
+
    Account cloned = (Account)MemberwiseClone();
+

          
+
    // 参照型フィールドの複製を作成する
+
    if (this.ContactAddresses != null)
+
    {
+
      cloned.ContactAddresses = (string[])this.ContactAddresses.Clone();
+
    }
+

          
+
    return cloned;
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Account a1, a2;
+

          
+
    a1 = new Account();
+
    a1.ID = 2;
+
    a1.Name = "Alice";
+
    a1.ContactAddresses = new string[] {"alice@example.com", "http://example.net/~alice/"};
+

          
+
    Console.WriteLine("a1 {0}:{1} ({2})", a1.ID, a1.Name, string.Join(", ", a1.ContactAddresses));
+

          
+
    a2 = a1.Clone(); // MemberwiseCloneを使ってコピー
+

          
+
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses));
+

          
+
    a1.ContactAddresses[0] = "bob@example.com"; // コピー元の参照型フィールドに変更を加える
+

          
+
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses));
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
~
Imports System
Class Musume
 

        

        
~
Class Account
    ' IClonableインターフェイスを実装
~
  Public ID As Integer
    Implements ICloneable
+
  Public Name As String
+
  Public ContactAddresses() As String
+

          
+
  Public Function Clone() As Account
+
    Dim cloned As Account = CType(MemberwiseClone(), Account)
+

          
+
    ' 参照型フィールドの複製を作成する
+
    If Not MyClass.ContactAddresses Is Nothing Then
+
      cloned.ContactAddresses = CType(MyClass.ContactAddresses.Clone(), String())
+
    End If
 

        

        
+
    Return cloned
+
  End Function
+
End Class
 

        

        
~
Class Sample
    ' 途中省略
+
  Shared Sub Main()
+
    Dim a1, a2 As Account
 

        

        
+
    a1 = New Account()
+
    a1.ID = 2
+
    a1.Name = "Alice"
+
    a1.ContactAddresses = New String() {"alice@example.com", "http://example.net/~alice/"}
 

        

        
~
    Console.WriteLine("a1 {0}:{1} ({2})", a1.ID, a1.Name, string.Join(", ", a1.ContactAddresses))
    ' 戻り値が型指定された公開されるClone()メソッド
-
    Public Function Clone() As Musume
 

        

        
~
    a2 = a1.Clone() ' MemberwiseCloneを使ってコピー
        Return DirectCast(Me.MemberwiseClone(), Musume)
 

        

        
~
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses))
    End Function
 

        

        
~
    a1.ContactAddresses(0) = "bob@example.com" ' コピー元の参照型フィールドに変更を加える
    ' 非公開のClone()メソッド
-
    Private Function CloneMyself() As Object Implements ICloneable.Clone
 

        

        
~
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses))
        Return Me.Clone()
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
    End Function
+
a1 2:Alice (alice@example.com, http://example.net/~alice/)
+
a2 2:Alice (alice@example.com, http://example.net/~alice/)
+
a2 2:Alice (alice@example.com, http://example.net/~alice/)
+
}}
 

        

        
~
なお、この例で配列の複製に使用している&msdn(netfx,method,System.Array.Clone){Array.Clone};メソッドも簡易コピーを行うメソッドです。 オブジェクトの完全な詳細コピーを作成するには、コピーしようとするフィールドを再帰的に複製していくことが必要になる場合があります。
End Class
+

          
+
**シリアライズによる複製
+
詳細コピーを行う方法の一つとして、シリアライズを利用する方法があります。 例として、バイナリシリアル化を用いて詳細コピーを行う例を挙げます。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.IO;
+
using System.Runtime.Serialization;
+
using System.Runtime.Serialization.Formatters.Binary;
+

          
+
[Serializable] // クラスをシリアル化可能にする
+
class Account
+
{
+
  public int ID;
+
  public string Name;
+
  public string[] ContactAddresses;
+

          
+
  public Account Clone()
+
  {
+
    // シリアル化した内容を保持しておくためのMemoryStreamを作成
+
    using (MemoryStream stream = new MemoryStream())
+
    {
+
      // バイナリシリアル化を行うためのフォーマッタを作成
+
      BinaryFormatter f = new BinaryFormatter();
+

          
+
      // 現在のインスタンスをシリアル化してMemoryStreamに格納
+
      f.Serialize(stream, this);
+

          
+
      // ストリームの位置を先頭に戻す
+
      stream.Position = 0L;
+

          
+
      // MemoryStreamに格納されている内容を逆シリアル化する
+
      return (Account)f.Deserialize(stream);
+
    }
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Account a1, a2;
+

          
+
    a1 = new Account();
+
    a1.ID = 2;
+
    a1.Name = "Alice";
+
    a1.ContactAddresses = new string[] {"alice@example.com", "http://example.net/~alice/"};
+

          
+
    Console.WriteLine("a1 {0}:{1} ({2})", a1.ID, a1.Name, string.Join(", ", a1.ContactAddresses));
+

          
+
    a2 = a1.Clone(); // MemberwiseCloneを使ってコピー
+

          
+
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses));
+

          
+
    a1.ContactAddresses[0] = "bob@example.com"; // コピー元の参照型フィールドに変更を加える
+

          
+
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses));
+
  }
+
}
 
}}
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.IO
+
Imports System.Runtime.Serialization
+
Imports System.Runtime.Serialization.Formatters.Binary
 

        

        
~
<Serializable> _ ' クラスをシリアル化可能にする
このように、公開されるClone()メソッドではそのクラス型を戻り値とし、IClonableインターフェイスのClone()メソッドでは厳密に型指定されたClone()メソッドの戻り値を利用することでインターフェイスの機能を保つこともできます。
+
Class Account
+
  Public ID As Integer
+
  Public Name As String
+
  Public ContactAddresses() As String
+

          
+
  Public Function Clone() As Account
+
    ' シリアル化した内容を保持しておくためのMemoryStreamを作成
+
    Using stream As New MemoryStream()
+
      ' バイナリシリアル化を行うためのフォーマッタを作成
+
      Dim f As New BinaryFormatter()
+

          
+
      ' 現在のインスタンスをシリアル化してMemoryStreamに格納
+
      f.Serialize(stream, Me)
+

          
+
      ' ストリームの位置を先頭に戻す
+
      stream.Position = 0L
+

          
+
      ' MemoryStreamに格納されている内容を逆シリアル化する
+
      Return CType(f.Deserialize(stream), Account)
+
    End Using
+
  End Function
+
End Class
 

        

        
~
Class Sample
**簡易コピーと詳細コピー
~
  Shared Sub Main()
先ほどMemberwiseClone()メソッドは「簡易コピー」であるとしました。 「簡易コピー」では、インスタンスの各フィールドがコピーされますが、参照型フィールドではその参照のみがコピーされ、値型フィールドでは通常の代入同様値がコピーされます。 反対に、「詳細コピー」では、値型フィールドは当然値がコピーされますが、参照型フィールドでも値型同様参照先のインスタンスのコピーが生成されます。
+
    Dim a1, a2 As Account
 

        

        
~
    a1 = New Account()
つまり、簡易コピーでは参照はコピーされるもののインスタンス全体がコピーされるのではなく、詳細コピーでは参照先のインスタンスを含めたすべてのフィールドのコピーが生成されます。 簡易コピーと詳細コピーのそれぞれを行った例を次に示します。
+
    a1.ID = 2
+
    a1.Name = "Alice"
+
    a1.ContactAddresses = New String() {"alice@example.com", "http://example.net/~alice/"}
 

        

        
~
    Console.WriteLine("a1 {0}:{1} ({2})", a1.ID, a1.Name, string.Join(", ", a1.ContactAddresses))
#code(vb,簡易コピーの例){{
-
Option Strict On
 

        

        
~
    a2 = a1.Clone() ' MemberwiseCloneを使ってコピー
Module ClonableAndDisposable
 

        

        
~
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses))
    ' Musumeから参照される型
-
    Class Group
 

        

        
~
    a1.ContactAddresses(0) = "bob@example.com" ' コピー元の参照型フィールドに変更を加える
        ' IClonableインターフェイスを実装
-
        Implements ICloneable
 

        

        
~
    Console.WriteLine("a2 {0}:{1} ({2})", a2.ID, a2.Name, string.Join(", ", a2.ContactAddresses))
        ' 値型フィールド
~
  End Sub
        Public Name As String
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        ' コンストラクタ
~
a1 2:Alice (alice@example.com, http://example.net/~alice/)
        Public Sub New(ByVal name As String)
+
a2 2:Alice (alice@example.com, http://example.net/~alice/)
+
a2 2:Alice (alice@example.com, http://example.net/~alice/)
+
}}
 

        

        
~
この方法の利点は、詳細コピー時の動作をシリアライズ属性などにより定義できる点、バイナリ単位で同一な複製を作成できる点です。 一方、フィールドがシリアライズをサポートしていない場合は複製できないという欠点もあります。 シリアライズの詳細については、[[programming/netfx2/overview/serialization]]で解説しています。
            Me.Name = name
 

        

        
~
**ICloneable
        End Sub
+
&msdn(netfx,type,System.ICloneable){ICloneableインターフェイス};はオブジェクトが複製可能であることを表すインターフェイスです。 このインターフェイスを実装することによって、型が複製を作成する能力を持つということを明示することが出来ます。 また、ICloneableインターフェイスには唯一のメソッド&msdn(netfx,type,System.ICloneable.Clone){Clone};が用意されていて、このメソッドを実装することでオブジェクトの複製を作成する機能を提供することが出来ます。
 

        

        
~
#tabpage(C#)
        ' 戻り値が型指定された公開されるClone()メソッド
~
#code(cs){{
        Public Function Clone() As Group
+
using System;
 

        

        
~
class Account : ICloneable
            Return DirectCast(Me.MemberwiseClone(), Group)
+
{
+
  public int ID;
+
  public string Name;
 

        

        
~
  // ICloneable.Cloneの実装
        End Function
+
  public object Clone()
+
  {
+
    return MemberwiseClone();
+
  }
+
}
 

        

        
~
class Sample
        ' 非公開のClone()メソッド
~
{
        Private Function CloneMyself() As Object Implements ICloneable.Clone
+
  static void Main()
+
  {
+
    Account a1, a2;
 

        

        
~
    a1 = new Account();
            Return Me.Clone()
+
    a1.ID = 2;
+
    a1.Name = "Alice";
 

        

        
~
    Console.WriteLine("a1 {0}:{1}", a1.ID, a1.Name);
        End Function
 

        

        
~
    a2 = (Account)a1.Clone(); // ICloneable.Cloneを使ってコピー
    End Class
 

        

        
+
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name);
 

        

        
~
    a1.ID = 3; // コピー元のメンバを変更
    ' コピー対象となるクラス
~
    a1.Name = "Bob";
    Class Musume
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name);
        ' IClonableインターフェイスを実装
~
  }
        Implements ICloneable
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
 

        

        
~
Class Account
        ' 値型フィールド
~
  Implements ICloneable
        Public Name As String
 

        

        
~
  Public ID As Integer
        ' 参照型フィールド
~
  Public Name As String
        Public Belonging As Group
 

        

        
~
  ' ICloneable.Cloneの実装
        ' コンストラクタ
~
  Public Function Clone() As Object Implements ICloneable.Clone
        Public Sub New(ByVal name As String, ByVal belonging As Group)
+
    Return MemberwiseClone()
+
  End Function
+
End Class
 

        

        
~
Class Sample
            Me.Name = name
~
  Shared Sub Main()
            Me.Belonging = belonging
+
    Dim a1, a2 As Account
 

        

        
~
    a1 = New Account()
        End Sub
+
    a1.ID = 2
+
    a1.Name = "Alice"
 

        

        
~
    Console.WriteLine("a1 {0}:{1}", a1.ID, a1.Name)
        ' 戻り値が型指定された公開されるClone()メソッド
-
        Public Function Clone() As Musume
 

        

        
~
    a2 = CType(a1.Clone(), Account) ' ICloneable.Cloneを使ってコピー
            Return DirectCast(Me.MemberwiseClone(), Musume)
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name)
        End Function
 

        

        
~
    a1.ID = 3 ' コピー元のメンバを変更
        ' 非公開のClone()メソッド
~
    a1.Name = "Bob"
        Private Function CloneMyself() As Object Implements ICloneable.Clone
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name)
            Return Me.Clone()
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        End Function
+
a1 2:Alice
+
a2 2:Alice
+
a2 2:Alice
+
}}
 

        

        
~
上記の例を見ても分かるとおり、ICloneable.Cloneメソッドの戻り値はObject型なので呼び出し側でのキャストが必要になります。 次の例のように、ICloneable.CloneメソッドをC#では明示的な実装、VBではプライベートなメソッドにし、複製処理の実装は戻り値は型定義したメソッドで行うようにすることでキャストが不要になります。
        ' インスタンスの状態を表示するためのメソッド
-
        Public Overrides Function ToString() As String
 

        

        
~
#tabpage(C#)
            If Me.Belonging Is Nothing Then
+
#code(cs){{
+
using System;
+

          
+
class Account : ICloneable
+
{
+
  public int ID;
+
  public string Name;
+

          
+
  // 複製を作成するメソッド
+
  public Account Clone()
+
  {
+
    return (Account)MemberwiseClone();
+
  }
+

          
+
  // ICloneable.Cloneの明示的な実装
+
  object ICloneable.Clone()
+
  {
+
    return Clone();
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    Account a1, a2;
+

          
+
    a1 = new Account();
+
    a1.ID = 2;
+
    a1.Name = "Alice";
+

          
+
    Console.WriteLine("a1 {0}:{1}", a1.ID, a1.Name);
+

          
+
    a2 = a1.Clone(); // 複製を作成
+

          
+
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name);
+

          
+
    a1.ID = 3; // コピー元のメンバを変更
+
    a1.Name = "Bob";
+

          
+
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name);
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+

          
+
Class Account
+
  Implements ICloneable
 

        

        
~
  Public ID As Integer
                Return Me.Name
+
  Public Name As String
+

          
+
  ' 複製を作成するメソッド
+
  Public Function Clone() As Object Implements ICloneable.Clone
+
    Return MemberwiseClone()
+
  End Function
+

          
+
  ' ICloneable.Cloneの実装
+
  Private Function CloneImpl() As Object Implements ICloneable.Clone
+
    Return Clone()
+
  End Function
+
End Class
 

        

        
~
Class Sample
            Else
+
  Shared Sub Main()
+
    Dim a1, a2 As Account
 

        

        
~
    a1 = New Account()
                Return Me.Name + " (" + Me.Belonging.Name + ")"
+
    a1.ID = 2
+
    a1.Name = "Alice"
 

        

        
~
    Console.WriteLine("a1 {0}:{1}", a1.ID, a1.Name)
            End If
 

        

        
~
    a2 = a1.Clone() ' 複製を作成
        End Function
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name)
    End Class
 

        

        
+
    a1.ID = 3 ' コピー元のメンバを変更
+
    a1.Name = "Bob"
 

        

        
~
    Console.WriteLine("a2 {0}:{1}", a2.ID, a2.Name)
    ' アプリケーションのエントリーポイント
~
  End Sub
    Sub Main()
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        ' 所属グループ
~
a1 2:Alice
        Dim g As New Group("モーニング娘。")
+
a2 2:Alice
+
a2 2:Alice
+
}}
 

        

        
~
ICloneable.Cloneメソッドでは、戻り値が複製されるオブジェクトと同じ型であることは求められていますが、複製の際に簡易コピーと詳細コピーのどちらが行われるかは実装次第となっています。 そのためICloneableを実装していても、Cloneメソッドで簡易コピーと詳細コピーのどちらが作成されるかは型によって異なることになります。 ICloneableインターフェイスはあくまで''複製を作成する機能''を提供するだけという点に留意する必要があります。
        ' 変数を宣言
-
        Dim m1, m2 As Musume
 

        

        
~
また、ICloneableインターフェイスにはICloneable<T>といったジェネリックなバージョンは用意されていません。 誤って複製元と互換性のない型を返そうとしてもコンパイル時には検出できない点にも注意が必要です。
        ' インスタンスを生成
-
        m1 = New Musume("加護亜依", g)
 

        

        
~
#googleadunit
        ' インスタンスの簡易コピーを生成
~
----
        m2 = m1.Clone
-
        m2.Name = "紺野あさ美"
 

        

        
~
*オブジェクトの破棄
        ' 状態を表示
~
.NET Frameworkでは、インスタンスがガベージコレクタによって解放される際にファイナライザが呼び出されます。 ファイナライザを明示的に呼び出すことは出来ず、マネージリソースはガベージコレクタによって解放されるため、ファイナライザをオーバーライドする必要もありません。 逆に、ガベージコレクタによって解放されないアンマネージリソースなどを解放するには、ファイナライザをオーバーライドしたり、Disposeメソッドを実装する必要があります。
        Console.WriteLine("m1: {0}", m1)
-
        Console.WriteLine("m2: {0}", m2)
 

        

        
~
**ファイナライザ
        ' Belongingメンバの値を比較
~
アンマネージリソースの解放処理は、ファイナライザ(&msdn(netfx,method,System.Object.Finalize){Object.Finalizeメソッド};)をオーバーライドして実装できます。 ガベージコレクタによるメモリ収集の負荷を増やす原因となるため、空のファイナライザを記述したり、解放すべきアンマネージリソースが無い場合など、不必要にオーバーライドすることは避けるべきです。 C#ではデストラクタ、VBではFinalizeメソッドを用いてファイナライザを実装します。 以下はコンストラクタで確保したアンマネージなメモリ領域をファイナライザで解放する例です。
        Console.WriteLine("m1.Belonging Is m2.Belonging: {0}", m1.Belonging Is m2.Belonging)
 

        

        
~
#tabpage(C#)
    End Sub
+
#code(cs){{
+
using System;
+
using System.Runtime.InteropServices;
+

          
+
class UnmanagedMemory
+
{
+
  public IntPtr Ptr;
+

          
+
  public UnmanagedMemory(int length)
+
  {
+
    Ptr = Marshal.AllocHGlobal(length);
+
  }
+

          
+
  // デストラクタ
+
  ~UnmanagedMemory()
+
  {
+
    Marshal.FreeHGlobal(Ptr);
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    UnmanagedMemory m = new UnmanagedMemory(4);
+

          
+
    Marshal.WriteInt32(m.Ptr, 16);
+

          
+
    Console.WriteLine(Marshal.ReadInt32(m.Ptr));
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Runtime.InteropServices
+

          
+
Class UnmanagedMemory
+
  Public Ptr As IntPtr
+

          
+
  Public Sub New(ByVal size As Integer)
+
    Ptr = Marshal.AllocHGlobal(size)
+
  End Sub
+

          
+
  Protected Overrides Sub Finalize()
+
    Marshal.FreeHGlobal(Ptr)
+
  End Sub
+
End Class
 

        

        
~
Class Sample
End Module
+
  Shared Sub Main()
+
    Dim m As New UnmanagedMemory(4)
+

          
+
    Marshal.WriteInt32(m.Ptr, 16)
+

          
+
    Console.WriteLine(Marshal.ReadInt32(m.Ptr))
+
  End Sub
+
End Class
 
}}
}}
+
#tabpage-end
 

        

        
~
(なお、外部から解放されたり変更されないようにUnmanagedMemory.Ptrは読み取り専用にすべきですが、例示のためパブリックフィールドとなっています)
#prompt(実行結果){{
~

          
m1: 加護亜依 (モーニング娘。)
~
ファイナライザはガベージコレクタによって呼び出され、明示的に呼び出すことは出来ないため、呼び出されるタイミングを制御することは出来ません。 次のコードでファイナライザが呼び出されるタイミングを調べてみます。
m2: 紺野あさ美 (モーニング娘。)
~

          
m1.Belonging Is m2.Belonging: True
~
#tabpage(C#)
Press any key to continue
+
#code(cs){{
+
using System;
+
using System.Diagnostics;
+

          
+
class Sample
+
{
+
  static Stopwatch sw = Stopwatch.StartNew();
+

          
+
  static void Print(string message)
+
  {
+
    Trace.WriteLine(string.Format("{0:D12}: {1}", sw.ElapsedTicks, message));
+
  }
+

          
+
  class TestObject
+
  {
+
    public TestObject()
+
    {
+
      Print("コンストラクタ");
+
    }
+

          
+
    ~TestObject()
+
    {
+
      Print("ファイナライザ");
+
    }
+
  }
+

          
+
  static void Create()
+
  {
+
    new TestObject();
+
  }
+

          
+
  static void Main()
+
  {
+
    Print("Main開始");
+

          
+
    Create();
+

          
+
    Print("Main終了");
+
  }
+
}
 
}}
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Diagnostics
 

        

        
~
Class Sample
このコードではGroupクラスにClone()メソッドが実装されていますが、使用されることはありません。 また、Musumeクラスで行われる MemberwiseClose()メソッドは、参照型フィールドが参照しているインスタンスのコピーまでは作成しないので、実行結果の通り、コピーにより生成されたインスタンスと元のインスタンスのBelongingメンバはともに同じインスタンスを参照しています。
+
  Shared Dim sw As Stopwatch = Stopwatch.StartNew()
 

        

        
~
  Shared Sub Print(ByVal message As String)
では、Musumeクラスで行われるコピー生成のコードを次のように変えた場合、すなわち、詳細コピーがなされるようにしたコードでその実行結果を見てみることにします。
+
    Trace.WriteLine(string.Format("{0:D12}: {1}", sw.ElapsedTicks, message))
+
  End Sub
+

          
+
  Class TestObject
+
    Public Sub New()
+
      Print("コンストラクタ")
+
    End Sub
 

        

        
~
    Protected Overrides Sub Finalize()
#code(vb,詳細コピーの例){{
~
      Print("ファイナライザ")
Class Musume
+
    End Sub
+
  End Class
 

        

        
+
  Shared Sub Create()
+
    Dim o As New TestObject()
+
  End Sub
 

        

        
~
  Shared Sub Main()
    ' 途中省略
+
    Print("Main開始")
 

        

        
+
    Create()
 

        

        
~
    Print("Main終了")
    ' 戻り値が型指定された公開されるClone()メソッド
~
  End Sub
    Public Function Clone() As Musume
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt(実行結果){{
        ' インスタンスの簡易コピーを作成
~
# 1回目
        Dim inst As Musume = DirectCast(Me.MemberwiseClone(), Musume)
+
000000002431: Main開始
+
000000861808: コンストラクタ
+
000000862052: Main終了
+
000000868126: ファイナライザ
+

          
+
# 2回目
+
000000002628: Main開始
+
000000889865: コンストラクタ
+
000000890115: Main終了
+
000000897718: ファイナライザ
+

          
+
# 3回目
+
000000003153: Main開始
+
000000959201: コンストラクタ
+
000000959446: Main終了
+
000000966561: ファイナライザ
+
}}
 

        

        
~
実行結果を見てわかるとおり、ファイナライザが呼び出されるタイミングは必ずしもメソッドから出た直後にはなりません。 C++などの言語では、メソッド内(ブロック)で使用された変数はメソッドが終了する時点で解放されますが、生成されたインスタンスはメソッドが終了した後も多少の間存在し続けていることがわかります。
        ' 参照型フィールドであるBelongingフィールドのコピーを生成
-
        If Not Me.Belonging Is Nothing Then
 

        

        
~
&msdn(netfx,type,System.GC.Collect){GC.Collectメソッド};を呼ぶことで強制的にガベージコレクションを実行させることが出来ます。 このメソッドはあまり頻繁に呼び出すべきものではありませんが、実験のため先の例を書き換えて、GC.Collectメソッドを呼び出すようにしてみます。
            inst.Belonging = Me.Belonging.Clone()
 

        

        
~
#tabpage(C#)
        End If
+
#code(cs){{
+
using System;
+
using System.Diagnostics;
+

          
+
class Sample
+
{
+
  static Stopwatch sw = Stopwatch.StartNew();
+

          
+
  static void Print(string message)
+
  {
+
    Trace.WriteLine(string.Format("{0:D12}: {1}", sw.ElapsedTicks, message));
+
  }
+

          
+
  class TestObject
+
  {
+
    public TestObject()
+
    {
+
      Print("コンストラクタ");
+
    }
+

          
+
    ~TestObject()
+
    {
+
      Print("ファイナライザ");
+
    }
+
  }
+

          
+
  static void Create()
+
  {
+
    new TestObject();
+
  }
+

          
+
  static void Main()
+
  {
+
    Print("Main開始");
+

          
+
    Create();
+

          
+
    Print("GC.Collect");
+

          
+
    GC.Collect();
+
    GC.WaitForPendingFinalizers();
+

          
+
    Print("Main終了");
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Diagnostics
 

        

        
~
Class Sample
        ' 生成されたコピーを返す
~
  Shared Dim sw As Stopwatch = Stopwatch.StartNew()
        Return inst
 

        

        
~
  Shared Sub Print(ByVal message As String)
    End Function
+
    Trace.WriteLine(string.Format("{0:D12}: {1}", sw.ElapsedTicks, message))
+
  End Sub
+

          
+
  Class TestObject
+
    Public Sub New()
+
      Print("コンストラクタ")
+
    End Sub
+

          
+
    Protected Overrides Sub Finalize()
+
      Print("ファイナライザ")
+
    End Sub
+
  End Class
+

          
+
  Shared Sub Create()
+
    Dim o As New TestObject()
+
  End Sub
+

          
+
  Shared Sub Main()
+
    Print("Main開始")
 

        

        
+
    Create()
 

        

        
~
    Print("GC.Collect")
    ' 途中省略
 

        

        
+
    GC.Collect()
+
    GC.WaitForPendingFinalizers()
 

        

        
+
    Print("Main終了")
+
  End Sub
 
End Class
End Class
 
}}
}}
+
#tabpage-end
 

        

        
 
#prompt(実行結果){{
#prompt(実行結果){{
~
# 1回目
m1: 加護亜依 (モーニング娘。)
~
000000002520: Main開始
m2: 紺野あさ美 (モーニング娘。)
~
000000834103: コンストラクタ
m1.Belonging Is m2.Belonging: False
~
000000834361: GC.Collect
Press any key to continue
+
000000842199: ファイナライザ
+
000000843322: Main終了
+

          
+
# 2回目
+
000000002423: Main開始
+
000000838771: コンストラクタ
+
000000839015: GC.Collect
+
000000845710: Main終了
+
000000851528: ファイナライザ
+

          
+
# 3回目
+
000000002466: Main開始
+
000000904019: コンストラクタ
+
000000904274: GC.Collect
+
000000912707: ファイナライザ
+
000000913824: Main終了
 
}}
}}
 

        

        
~
このように、(必ずではありませんが)GC.Collectにより参照されなくなったオブジェクトのファイナライザが呼び出されるようになります。 なお、上記の例では、&msdn(netfx,type,System.GC.WaitForPendingFinalizers){GC.WaitForPendingFinalizersメソッド};で収集対象となっているファイナライザの実行が終了するまで待機しています。
このように、詳細コピーでは参照型のフィールドが参照しているインスタンスのコピーも作成します。 そのため、Belongingフィールドの参照しているインスタンス自体は異なるものの、そのインスタンスのフィールドの値は全く同じものになります。
 

        

        
~
**IDisposable
最後に、今まで説明してきませんでしたが、これらの方法でコピーされるのはインスタンスのフィールドであって、静的なフィールドは当然コピー対象ではありません。
+
ファイナライザは呼び出されるタイミングを事前に知ることは出来ないため、リソースを速やかに解放したい場合にはファイナライザに頼らない方法で解放処理を実装する必要があります。 この様な目的に使用できるのが&msdn(netfx,type,System.IDisposable){IDisposableインターフェイス};です。 このインターフェイスは、型が解放すべきリソースを保持していることを表すと同時に、それを解放するためのメソッド&msdn(netfx,method,System.IDisposable.Dispose){Dispose};を提供します。
 

        

        
~
以下の例は、先に挙げた例にIDisposableインターフェイスを実装したものです。
*Finalize()メソッド(デストラクタ)
-
これまではオブジェクトのコピーについて見てきましたが、ここからはオブジェクトの破棄と消滅に関することを見ていきたいと思います。 まず、.NET Frameworkによって提供されるクラスには、コンストラクタと同様にデストラクタも存在します。 これは Finalize() メソッドとして実装されます。 しかし、これらがあまり用いられることがないのは、マネージ・リソースはガベージコレクタによって開放されるからです。 ですから、逆にこれらのものが必要になるのはアンマネージ・リソースを使う場合など、用途は限られます。 このFinalize()メソッドを実装した例とそれが呼び出されるタイミングを、コンストラクタが呼び出されるタイミングとともに見てみたいと思います。
 

        

        
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Runtime.InteropServices;
+

          
+
class UnmanagedMemory : IDisposable
+
{
+
  public IntPtr Ptr;
+

          
+
  public UnmanagedMemory(int size)
+
  {
+
    Ptr = Marshal.AllocHGlobal(size);
+
  }
+

          
+
  // IDisposable.Disposeの実装
+
  public void Dispose()
+
  {
+
    if (Ptr != IntPtr.Zero)
+
    {
+
      Marshal.FreeHGlobal(Ptr);
+

          
+
      // 解放済みであることを示すためにIntPtr.Zeroを代入
+
      Ptr = IntPtr.Zero;
+
    }
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    UnmanagedMemory m = new UnmanagedMemory(4);
+

          
+
    Marshal.WriteInt32(m.Ptr, 16);
+

          
+
    Console.WriteLine(Marshal.ReadInt32(m.Ptr));
+

          
+
    // Disposeメソッドでアンマネージリソースを解放する
+
    m.Dispose();
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
~
Imports System
Module ClonableAndDisposable
+
Imports System.Runtime.InteropServices
 

        

        
~
Class UnmanagedMemory
    Class SampleClass
+
  Implements IDisposable
 

        

        
~
  Public Ptr As IntPtr
        Public Sub New()
 

        

        
~
  Public Sub New(ByVal size As Integer)
            OutputMessage("コンストラクタが呼び出されました。")
+
    Ptr = Marshal.AllocHGlobal(size)
+
  End Sub
+

          
+
  ' IDisposable.Disposeの実装
+
  Public Sub Dispose() Implements IDisposable.Dispose
+
    If Ptr <> IntPtr.Zero
+
      Marshal.FreeHGlobal(Ptr)
+

          
+
      ' 解放済みであることを示すためにIntPtr.Zeroを代入
+
      Ptr = IntPtr.Zero
+
    End If
+
  End Sub
+
End Class
 

        

        
~
Class Sample
        End Sub
+
  Shared Sub Main()
+
    Dim m As New UnmanagedMemory(4)
 

        

        
~
    Marshal.WriteInt32(m.Ptr, 16)
        Protected Overrides Sub Finalize()
 

        

        
~
    Console.WriteLine(Marshal.ReadInt32(m.Ptr))
            OutputMessage("デストラクタが呼び出されました。")
 

        

        
~
    ' Disposeメソッドでアンマネージリソースを解放する
        End Sub
+
    m.Dispose()
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
このように、Disposeメソッドを呼び出すことで明示的にリソースの解放などを行えるようになります。
    End Class
 

        

        
~
***IDisposableとファイナライザ
    ' アプリケーションのエントリーポイント
~
IDisposableインターフェイスは、あくまでリソースの解放する手段を提供するためのもので、リソースを確実に解放することを保証するためのものではありません。 IDisposableを実装することで明示的にリソースを解放することは出来ますが、Disposeメソッドが呼び出されない場合はリソースが解放されないままとなってしまいます。 単にDisposeメソッドの呼び出しをし忘れた場合や、途中で例外がスローされてDisposeメソッドの呼び出しが行われない場合などにこのような状況が発生し得ます。
    Sub Main()
 

        

        
~
そこでIDisposableを実装する場合は、Disposeメソッドが呼び出されなかった場合でも最終的にガベージコレクタがオブジェクトを解放する時点でリソースが解放されるよう、ファイナライザもオーバーライドします。
        OutputMessage("アプリケーションが開始されました。")
 

        

        
~
#tabpage(C#)
        OutputMessage("インスタンスを生成します。")
+
#code(cs){{
+
using System;
+
using System.Diagnostics;
+
using System.Runtime.InteropServices;
 

        

        
~
class UnmanagedMemory : IDisposable
        Dim inst As New SampleClass()
+
{
+
  public IntPtr Ptr;
 

        

        
~
  public UnmanagedMemory(int size)
        OutputMessage("インスタンスが生成されました。")
+
  {
+
    Ptr = Marshal.AllocHGlobal(size);
 

        

        
~
    Trace.WriteLine(string.Format("allocated: Ptr = {0}", Ptr));
        OutputMessage("アプリケーションが終了しました。")
+
  }
 

        

        
~
  ~UnmanagedMemory()
    End Sub
+
  {
+
    Free();
 

        

        
~
    Trace.WriteLine(string.Format("finalized: Ptr = {0}", Ptr));
    ' メッセージを表示するメソッド
~
  }
    Sub OutputMessage(ByVal msg As String)
 

        

        
~
  public void Dispose()
        Debug.WriteLine("Ticks " + DateTime.Now.Ticks.ToString() + ": " + msg)
+
  {
+
    Free();
 

        

        
~
    Trace.WriteLine(string.Format("disposed: Ptr = {0}", Ptr));
    End Sub
+
  }
 

        

        
~
  private void Free()
End Module
~
  {
}}
+
    if (Ptr != IntPtr.Zero)
+
    {
+
      Marshal.FreeHGlobal(Ptr);
 

        

        
~
      // 解放済みであることを示すためにIntPtr.Zeroを代入
#prompt(実行結果){{
~
      Ptr = IntPtr.Zero;
・一回目
~
    }
Ticks 631849925642777328: アプリケーションが開始されました。
~
  }
Ticks 631849925650588560: インスタンスを生成します。
~
}
Ticks 631849925650688704: コンストラクタが呼び出されました。
-
Ticks 631849925650688704: インスタンスが生成されました。
-
Ticks 631849925650688704: アプリケーションが終了しました。
-
Ticks 631849925650888992: デストラクタが呼び出されました。
 

        

        
~
class Sample
・二回目
~
{
Ticks 631849925719988352: アプリケーションが開始されました。
~
  static void Main()
Ticks 631849925727799584: インスタンスを生成します。
~
  {
Ticks 631849925727799584: コンストラクタが呼び出されました。
~
    UnmanagedMemory m = new UnmanagedMemory(4);
Ticks 631849925727899728: インスタンスが生成されました。
-
Ticks 631849925727899728: アプリケーションが終了しました。
-
Ticks 631849925728100016: デストラクタが呼び出されました。
 

        

        
~
    Marshal.WriteInt32(m.Ptr, 16);
・三回目
~

          
Ticks 631849925852879440: アプリケーションが開始されました。
~
    Console.WriteLine(Marshal.ReadInt32(m.Ptr));
Ticks 631849925860490384: インスタンスを生成します。
~

          
Ticks 631849925860590528: コンストラクタが呼び出されました。
~
    // Disposeによる明示的な解放を行わない
Ticks 631849925860790816: インスタンスが生成されました。
~
    //m.Dispose();
Ticks 631849925860790816: アプリケーションが終了しました。
~
  }
Ticks 631849925860991104: デストラクタが呼び出されました。
+
}
 
}}
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Diagnostics
+
Imports System.Runtime.InteropServices
 

        

        
~
Class UnmanagedMemory
C++などの言語では、ブロック内で使用された変数はブロックから出る時点で解放されますが、実行結果を見てわかるとおり、生成されたインスタンスはブロックから出た後も多少の間残っていることがわかります。 また、解放されるタイミングも必ずしもブロックから出た直後ではないようです。 何度か同じコードを実行しましたが、多くの場合ブロックから出てからデストラクタが呼び出されるまで多少のラグがあるようです。
+
  Implements IDisposable
 

        

        
~
  Public Ptr As IntPtr
ちなみに、このサンプルでメッセージを表示するのにConsoleクラスではなくDebugクラスを使用しているのは、アプリケーションの終了時にConsoleクラスが解放されるからです。 Debugクラスはプログラムの実行中であれば解放されていません。
 

        

        
~
  Public Sub New(ByVal size As Integer)
*IDisposableインターフェイス
~
    Ptr = Marshal.AllocHGlobal(size)
このように、Finalize()メソッド (デストラクタ)では、どのタイミングで呼び出されるかがわからないので、動的にメモリ確保を行ったりする場合などには非常に不便です。 そこで、明示的にリソースを解放するための手段として、IDisposableインターフェイスを利用するという方法があります。 IDisposableインターフェイスは唯一のメソッドであるDispose()メソッドを提供します。 IDisposableインターフェイスを利用した例を次に示します。
 

        

        
~
    Trace.WriteLine(string.Format("allocated: Ptr = {0}", Ptr))
#code(vb){{
~
  End Sub
Module ClonableAndDisposable
 

        

        
~
  Protected Overrides Sub Finalize()
    Class SampleClass
+
    Free()
 

        

        
~
    Trace.WriteLine(string.Format("finalized: Ptr = {0}", Ptr))
        ' IDisposableインターフェイスを実装
~
  End Sub
        Implements IDisposable
 

        

        
~
  Public Sub Dispose() Implements IDisposable.Dispose
        Public Sub New()
+
    Free()
 

        

        
~
    Trace.WriteLine(string.Format("disposed: Ptr = {0}", Ptr))
            OutputMessage("コンストラクタが呼び出されました。")
+
  End Sub
 

        

        
~
  Private Sub Free()
        End Sub
+
    If Ptr <> IntPtr.Zero
+
      Marshal.FreeHGlobal(Ptr)
 

        

        
~
      ' 解放済みであることを示すためにIntPtr.Zeroを代入
        Public Sub Dispose() Implements IDisposable.Dispose
+
      Ptr = IntPtr.Zero
+
    End If
+
  End Sub
+
End Class
 

        

        
~
Class Sample
            OutputMessage("Dispose()メソッドが呼び出されました")
+
  Shared Sub Main()
+
    Dim m As New UnmanagedMemory(4)
 

        

        
~
    Marshal.WriteInt32(m.Ptr, 16)
        End Sub
 

        

        
~
    Console.WriteLine(Marshal.ReadInt32(m.Ptr))
    End Class
 

        

        
~
    ' Disposeによる明示的な解放を行わない
    ' アプリケーションのエントリーポイント
~
    'm.Dispose()
    Sub Main()
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
#prompt{{
        OutputMessage("アプリケーションが開始されました。")
+
allocated: Ptr = 150808256
+
16
+
finalized: Ptr = 0
+
}}
 

        

        
~
このように実装することでDisposeが呼ばれない場合でも確実に解放するように出来ます。
        OutputMessage("インスタンスを生成します。")
 

        

        
~
しかし、Disposeが呼ばれた場合には既にリソースが解放済みであるにも関わらず、ファイナライザの不要な呼び出しが発生します。 上記の例でコメントアウトしているDisposeメソッドの呼び出しを有効にして実行すると、次のような結果になります。
        Dim inst As New SampleClass()
 

        

        
~
#prompt{{
        OutputMessage("インスタンスが生成されました。")
+
allocated: Ptr = 150643120
+
16
+
disposed: Ptr = 0
+
finalized: Ptr = 0
+
}}
 

        

        
~
そこで、Disposeメソッドが呼び出された場合にはファイナライザの実行が不要であることをガベージコレクタに伝えるため、Disposeメソッドで&msdn(netfx,type,System.GC.SuppressFinalize){GC.SuppressFinalizeメソッド};を呼び出します。 先の例におけるDisposeメソッドに、次のようなGC.SuppressFinalizeメソッドの呼び出しを追加します。
        OutputMessage("インスタンスを解放します。")
 

        

        
~
#tabpage(C#)
        inst.Dispose()
+
#code(cs){{
+
  public void Dispose()
+
  {
+
    Free();
 

        

        
~
    // このインスタンスに対するファイナライザの呼び出しが不要であることを伝える
        OutputMessage("インスタンスを解放されました。")
+
    GC.SuppressFinalize(this);
 

        

        
~
    Trace.WriteLine(string.Format("disposed: Ptr = {0}", Ptr));
        OutputMessage("アプリケーションが終了しました。")
+
  }
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
  Public Sub Dispose() Implements IDisposable.Dispose
+
    Free()
 

        

        
~
    ' このインスタンスに対するファイナライザの呼び出しが不要であることを伝える
    End Sub
+
    GC.SuppressFinalize(Me)
+

          
+
    Trace.WriteLine(string.Format("disposed: Ptr = {0}", Ptr))
+
  End Sub
+
}}
+
#tabpage-end
 

        

        
~
すると、Disposeメソッドが呼び出された場合はファイナライザの呼び出しはされなくなります。
    ' メッセージを表示するメソッド
-
    Sub OutputMessage(ByVal msg As String)
 

        

        
~
#prompt{{
        Debug.WriteLine("Ticks " + DateTime.Now.Ticks.ToString() + ": " + msg)
+
allocated: Ptr = 142677440
+
16
+
disposed: Ptr = 0
+
}}
 

        

        
~
***解放されたリソースへのアクセス拒否
    End Sub
+
Disposeメソッドにより任意のタイミングでリソースが解放できるようになると、既に解放されているリソースにアクセスしようとしてしまう場合が発生します。 そのような操作を許可しない場合にスローする例外が&msdn(netfx,type,System.ObjectDisposedException){ObjectDisposedException};です。 ObjectDisposedExceptionはコンストラクタの引数で例外をスローしたオブジェクトの名前を指定できるので、&msdn(netfx,type,System.Type.FullName){Type.FullName};などを指定します。
 

        

        
~
以下の例は、解放済みのリソースにアクセスしようとした場合にObjectDisposedExceptionをスローするようにしたものです。
End Module
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.Diagnostics;
+
using System.Runtime.InteropServices;
+

          
+
class UnmanagedMemory : IDisposable
+
{
+
  private IntPtr ptr;
+

          
+
  public IntPtr Ptr
+
  {
+
    get
+
    {
+
      ThrowIfDisposed();
+
      return ptr;
+
    }
+
  }
+

          
+
  public UnmanagedMemory(int size)
+
  {
+
    ptr = Marshal.AllocHGlobal(size);
+
  }
+

          
+
  ~UnmanagedMemory()
+
  {
+
    Free();
+
  }
+

          
+
  public void Dispose()
+
  {
+
    Free();
+
    GC.SuppressFinalize(this);
+
  }
+

          
+
  private void Free()
+
  {
+
    if (ptr != IntPtr.Zero)
+
    {
+
      Marshal.FreeHGlobal(ptr);
+

          
+
      ptr = IntPtr.Zero;
+
    }
+
  }
+

          
+
  private void ThrowIfDisposed()
+
  {
+
    // 解放済みの場合はObjectDisposedExceptionをスローする
+
    if (ptr == IntPtr.Zero) throw new ObjectDisposedException(GetType().FullName);
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    UnmanagedMemory m = new UnmanagedMemory(4);
+

          
+
    // リソースを解放する
+
    m.Dispose();
+

          
+
    // 解放済みのリソースへのアクセスを試みる
+
    Marshal.WriteInt32(m.Ptr, 16);
+
  }
+
}
 
}}
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Diagnostics
+
Imports System.Runtime.InteropServices
+

          
+
Class UnmanagedMemory
+
  Implements IDisposable
+

          
+
  Private _ptr As IntPtr
+

          
+
  Public ReadOnly Property Ptr As IntPtr
+
    Get
+
      ThrowIfDisposed()
+
      Return _ptr
+
    End Get
+
  End Property
+

          
+
  Public Sub New(ByVal size As Integer)
+
    _ptr = Marshal.AllocHGlobal(size)
+
  End Sub
+

          
+
  Protected Overrides Sub Finalize()
+
    Free()
+
  End Sub
+

          
+
  Public Sub Dispose() Implements IDisposable.Dispose
+
    Free()
+
    GC.SuppressFinalize(Me)
+
  End Sub
+

          
+
  Private Sub Free()
+
    If _ptr <> IntPtr.Zero
+
      Marshal.FreeHGlobal(_ptr)
+

          
+
      _ptr = IntPtr.Zero
+
    End If
+
  End Sub
+

          
+
  Private Sub ThrowIfDisposed()
+
    ' 解放済みの場合はObjectDisposedExceptionをスローする
+
    If _ptr = IntPtr.Zero Then Throw New ObjectDisposedException(Me.GetType().FullName)
+
  End Sub
+
End Class
 

        

        
~
Class Sample
#prompt(実行結果){{
~
  Shared Sub Main()
Ticks 631849931316335504: アプリケーションが開始されました。
~
    Dim m As New UnmanagedMemory(4)
Ticks 631849931324747600: インスタンスを生成します。
~

          
Ticks 631849931324747600: コンストラクタが呼び出されました。
~
    ' リソースを解放する
Ticks 631849931324747600: インスタンスが生成されました。
~
    m.Dispose()
Ticks 631849931324847744: インスタンスを解放します。
~

          
Ticks 631849931324847744: Dispose()メソッドが呼び出されました
~
    ' 解放済みのリソースへのアクセスを試みる
Ticks 631849931324947888: インスタンスを解放されました。
~
    Marshal.WriteInt32(m.Ptr, 16)
Ticks 631849931325148176: アプリケーションが終了しました。
+
  End Sub
+
End Class
 
}}
}}
+
#tabpage-end
+

          
+
#prompt{{
+
ハンドルされていない例外: System.ObjectDisposedException: 破棄されたオブジェクトにアクセスできません。
+
オブジェクト名 'UnmanagedMemory' です。
+
   場所 UnmanagedMemory.ThrowIfDisposed()
+
   場所 UnmanagedMemory.get_Ptr()
+
   場所 Sample.Main()
+
}}
+

          
+
なお、IDisposable.DisposeメソッドからはObjectDisposedExceptionをスローしないようにします。 IDisposable.Disposeは複数回呼び出されても例外をスローすることなく動作するように実装しなければなりません。
 

        

        
~
**ファイナライザとIDisposableのデザインパターン
実行結果を見てわかるとおり、Dispose()メソッドを呼び出すことによって明示的にリソースの解放などを行えるようになります。 後はこのメソッドにリソース解放のコードを記述すればよいだけです。 また、IDisposableインターフェイスを実装する場合は、Dispose()メソッドが呼び出されなかった場合に備えて、Finalize()メソッドも実装している必要があります。 また、この場合のFinalize()メソッドは次の例のようにDispose()を呼び出す場合が多いと思います。
+
.NET Frameworkでは、ファイナライザとIDisposableインターフェイスを実装するクラスのデザインパターンが提示されています。 このパターンでは、IDisposableを実装するクラスを継承する場合についても示されています。 ファイナライザとIDisposableを実装する際のデザインパターンの詳細については以下のページで解説されています。
 

        

        
+
-&msdn(netfx,id,498928w2){アンマネージ リソースのクリーンアップ};
+
--&msdn(netfx,id,fs2xkftw){Dispose メソッドの実装};
+
--&msdn(netfx,id,0s71x931){Finalize メソッドおよびデストラクター};
+
--&msdn(netfx,id,ddae83kx){Finalize メソッドのオーバーライド};
+

          
+
以下の例は、IDisposableを実装するクラスと、それを継承したクラスを上記のデザインパターンに従って実装したものです。
+

          
+
#tabpage(C#)
+
#code(cs){{
+
using System;
+
using System.IO;
+
using System.Runtime.InteropServices;
+

          
+
class DisposableBase : IDisposable
+
{
+
  private bool disposed = false; // リソースが破棄(解放)されていることを表すフラグ
+
  private IntPtr ptr = Marshal.AllocHGlobal(4); // コンストラクタ等で確保されるアンマネージリソースと仮定
+

          
+
  // ファイナライザ
+
  ~DisposableBase()
+
  {
+
    // アンマネージリソースのみを破棄させる
+
    Dispose(false);
+
  }
+

          
+
  // IDisposable.Disposeの実装
+
  public void Dispose()
+
  {
+
    // アンマネージリソースと、マネージリソースの両方を破棄させる
+
    Dispose(true);
+
    GC.SuppressFinalize(this);
+
  }
+

          
+
  // リソースの解放処理を行うためのメソッド
+
  protected virtual void Dispose(bool disposing)
+
  {
+
    // 既にリソースが破棄されている場合は何もしない
+
    if (disposed) return;
+

          
+
    // 破棄されていないアンマネージリソースの解放処理を行う
+
    if (ptr != IntPtr.Zero)
+
    {
+
      Marshal.FreeHGlobal(ptr);
+
      ptr = IntPtr.Zero;
+
    }
+

          
+
    // リソースは破棄されている
+
    disposed = true;
+
  }
+

          
+
  // 既にリソースが破棄されているかチェックして、ObjectDisposedExceptionをスローするためのメソッド
+
  protected void ThrowIfDisposed()
+
  {
+
    if (disposed) throw new ObjectDisposedException(GetType().FullName);
+
  }
+

          
+
  // 保持しているリソースを使って何らかの操作を行うメソッドと仮定
+
  public virtual string SomeOperation()
+
  {
+
    // 既にリソースが破棄されているかチェックする
+
    ThrowIfDisposed();
+

          
+
    return "operation result";
+
  }
+
}
+

          
+
class DisposableExtended : DisposableBase
+
{
+
  private IntPtr extraDataPtr = Marshal.AllocHGlobal(8); // コンストラクタ等で確保されるアンマネージリソースと仮定
+
  private Stream resourceStream = Stream.Null; // コンストラクタ等で確保されるマネージリソースと仮定
+

          
+
  // 基底クラスのリソース解放処理をオーバーライド
+
  protected override void Dispose(bool disposing)
+
  {
+
    try
+
    {
+
      // アンマネージリソースと、マネージリソースの両方の破棄が要求された場合
+
      if (disposing)
+
      {
+
        // 破棄されていないマネージリソースの解放処理を行う
+
        if (resourceStream != null)
+
        {
+
          resourceStream.Close();
+
          resourceStream = null;
+
        }
+
      }
+

          
+
      // 破棄されていないアンマネージリソースの解放処理を行う
+
      if (extraDataPtr != IntPtr.Zero)
+
      {
+
        Marshal.FreeHGlobal(extraDataPtr);
+
        extraDataPtr = IntPtr.Zero;
+
      }
+
    }
+
    finally
+
    {
+
      // 例外が発生しても基底クラスのDisposeメソッドを確実に呼び出すようにする
+
      base.Dispose(disposing);
+
    }
+
  }
+

          
+
  public override string SomeOperation()
+
  {
+
    // 既にリソースが破棄されているかチェックする
+
    ThrowIfDisposed();
+

          
+
    return "operation result";
+
  }
+
}
+
}}
+
#tabpage(VB)
 
#code(vb){{
#code(vb){{
+
Imports System
 
Imports System.IO
Imports System.IO
+
Imports System.Runtime.InteropServices
 

        

        
~
Class DisposableBase
Module ClonableAndDisposable
~
  Implements IDisposable

          
-
    Class SampleClass
 

        

        
~
  Private disposed As Boolean = False ' リソースが破棄(解放)されていることを表すフラグ
        ' IDisposableインターフェイスを実装
~
  Private ptr As IntPtr = Marshal.AllocHGlobal(4) ' コンストラクタ等で確保されるアンマネージリソースと仮定
        Implements IDisposable
 

        

        
~
  ' ファイナライザ
        ' すでにインスタンスが解放されたかを保存する
~
  Protected Overrides Sub Finalize()
        Private disposed As Boolean
+
    ' アンマネージリソースのみを破棄させる
+
    Dispose(false)
+
  End Sub
+

          
+
  ' IDisposable.Disposeの実装
+
  Public Sub Dispose() Implements IDisposable.Dispose
+
    ' アンマネージリソースと、マネージリソースの両方を破棄させる
+
    Dispose(true)
+
    GC.SuppressFinalize(Me)
+
  End Sub
+

          
+
  ' リソースの解放処理を行うためのメソッド
+
  Protected Overridable Sub Dispose(ByVal disposing As Boolean)
+
    ' 既にリソースが破棄されている場合は何もしない
+
    If disposed Then Return
+

          
+
    ' 破棄されていないアンマネージリソースの解放処理を行う
+
    If ptr <> IntPtr.Zero Then
+
      Marshal.FreeHGlobal(ptr)
+
      ptr = IntPtr.Zero
+
    End If
+

          
+
    ' リソースは破棄されている
+
    disposed = True
+
  End Sub
+

          
+
  ' 既にリソースが破棄されているかチェックして、ObjectDisposedExceptionをスローするためのメソッド
+
  Protected Sub ThrowIfDisposed()
+
    If disposed Then Throw New ObjectDisposedException(Me.GetType().FullName)
+
  End Sub
+

          
+
  ' 保持しているリソースを使って何らかの操作を行うメソッドと仮定
+
  Public Overridable Function SomeOperation() As String
+
    ' 既にリソースが破棄されているかチェックする
+
    ThrowIfDisposed()
 

        

        
~
    Return "operation result"
        ' コンストラクタ
~
  End Function
        Public Sub New()
+
End Class
 

        

        
~
Class DisposableExtended
            disposed = False
+
  Inherits DisposableBase
 

        

        
~
  Private extraDataPtr As IntPtr = Marshal.AllocHGlobal(8) ' コンストラクタ等で確保されるアンマネージリソースと仮定
            OutputMessage("コンストラクタが呼び出されました")
+
  Private resourceStream As Stream = Stream.Null ' コンストラクタ等で確保されるマネージリソースと仮定
 

        

        
~
  ' 基底クラスのリソース解放処理をオーバーライド
        End Sub
+
  Protected Overrides Sub Dispose(ByVal disposing As Boolean)
+
    Try
+
      ' アンマネージリソースと、マネージリソースの両方の破棄が要求された場合
+
      If disposing Then
+
        ' 破棄されていないマネージリソースの解放処理を行う
+
        If Not resourceStream Is Nothing Then
+
          resourceStream.Close()
+
          resourceStream = Nothing
+
        End If
+
      End If
 

        

        
~
      ' 破棄されていないアンマネージリソースの解放処理を行う
        ' デストラクタ
~
      If extraDataPtr <> IntPtr.Zero Then
        Protected Overrides Sub Finalize()
+
        Marshal.FreeHGlobal(extraDataPtr)
+
        extraDataPtr = IntPtr.Zero
+
      End If
+
    Finally
+
      ' 例外が発生しても基底クラスのDisposeメソッドを確実に呼び出すようにする
+
      MyBase.Dispose(disposing)
+
    End Try
+
  End Sub
+

          
+
  Public Overrides Function SomeOperation() As String
+
    ' 既にリソースが破棄されているかチェックする
+
    ThrowIfDisposed()
 

        

        
~
    Return "operation result"
            Dispose()
+
  End Function
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
**usingステートメント
        End Sub
+
usingステートメントはC#とVBで用意されているもので、try-finally文を使った確実な解放処理をより簡単に記述することが出来ます。 解放処理が必要になる例として、FileStreamを使用する場合を考えます。 try-finally文を使った場合、開いたストリームを確実に閉じるようにするには次のように記述できます。
 

        

        
~
#tabpage(C#)
        ' Dispose()メソッド
~
#code(cs){{
        Public Sub Dispose() Implements IDisposable.Dispose
+
using System;
+
using System.IO;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    FileStream stream = null;
+

          
+
    try
+
    {
+
      stream = File.OpenRead("test.txt");
+

          
+
      StreamReader reader = new StreamReader(stream);
+

          
+
      Console.WriteLine(reader.ReadToEnd());
+
    }
+
    finally
+
    {
+
      if (stream != null) stream.Close();
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.IO
 

        

        
~
Class Sample
            If Not disposed Then
+
  Shared Sub Main()
+
    Dim stream As FileStream = Nothing
+

          
+
    Try
+
      stream = File.OpenRead("test.txt")
+

          
+
      Dim reader As New StreamReader(stream)
+

          
+
      Console.WriteLine(reader.ReadToEnd())
+
    Finally
+
      If Not stream Is Nothing Then stream.Close()
+
    End Try
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
usingステートメントを用いると、上記のコードと同等の処理を以下のように記述することが出来ます。
                OutputMessage("Dispose()メソッドが呼び出されました")
 

        

        
~
#tabpage(C#)
                disposed = True
+
#code(cs){{
+
using System;
+
using System.IO;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    using (FileStream stream = File.OpenRead("test.txt"))
+
    {
+
      StreamReader reader = new StreamReader(stream);
+

          
+
      Console.WriteLine(reader.ReadToEnd());
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.IO
 

        

        
~
Class Sample
            End If
+
  Shared Sub Main()
+
    Using stream As FileStream = File.OpenRead("test.txt")
+
      Dim reader As New StreamReader(stream)
+

          
+
      Console.WriteLine(reader.ReadToEnd())
+
    End Using
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
usingステートメントはIDisposableインターフェイスを実装している型ならどのような型に対しても使用することができ、そのスコープから抜ける時点でDisposeメソッドが自動的に呼び出されるようになります。 また、usingステートメントはtry-finally文に相当する処理に展開されるため、usingステートメント内で例外が発生した場合でも確実にDisposeメソッドが呼び出されます。 FileStreamクラスの基底クラスである&msdn(netfx,type,System.IO.Stream){Streamクラス};はIDisposableを実装しているため、usingステートメントを使った記述ができるようになっています。
        End Sub
 

        

        
~
usingステートメントを使うと、確実な解放処理のための記述がtry-finally文よりシンプルになるほか、解放可能なリソースを持つオブジェクトのスコープ(有効範囲)と生存期間が明確になるという利点もあります。
    End Class
 

        

        
~
なお、usingステートメントでは複数のオブジェクトを指定することも出来ます。 以下の例はusingステートメントで二つのStreamを使用した例です。 (この例で使用している&msdn(netfx,method,System.IO.Stream.CopyTo){CopyToメソッド};は.NET Framework 4以降で使用可能なメソッドです)
    ' アプリケーションのエントリーポイント
-
    Sub Main()
 

        

        
~
#tabpage(C#)
        OutputMessage("アプリケーションが開始されました。")
+
#code(cs){{
+
using System;
+
using System.IO;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    using (Stream fromStream = File.OpenRead("source.txt"), toStream = File.OpenWrite("dest.txt"))
+
    {
+
      // ファイルsource.txtの内容をdest.txtにコピーする
+
      fromStream.CopyTo(toStream);
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.IO
 

        

        
~
Class Sample
        OutputMessage("インスタンスが作成されます。")
+
  Shared Sub Main()
+
    Using fromStream As FileStream = File.OpenRead("source.txt"), toStream As FileStream = File.OpenWrite("dest.txt")
+
      ' ファイルsource.txtの内容をdest.txtにコピーする
+
      fromStream.CopyTo(toStream)
+
    End Using
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
C#では、これを次のようにも記述できます。
        Dim inst As New SampleClass()
 

        

        
~
#code(cs){{
        OutputMessage("インスタンスが作成されました。")
+
using System;
+
using System.IO;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    using (Stream fromStream = File.OpenRead("source.txt"))
+
    using (Stream toStream = File.OpenWrite("dest.txt"))
+
    {
+
      fromStream.CopyTo(toStream);
+
    }
+
  }
+
}
+
}}
 

        

        
~
もちろん、C#・VBともに入れ子にして記述することも出来ます。
        OutputMessage("アプリケーションが終了されます。")
 

        

        
~
#tabpage(C#)
    End Sub
+
#code(cs){{
+
using System;
+
using System.IO;
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    using (Stream fromStream = File.OpenRead("source.txt"))
+
    {
+
      using (Stream toStream = File.OpenWrite("dest.txt"))
+
      {
+
        fromStream.CopyTo(toStream);
+
      }
+
    }
+
  }
+
}
+
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.IO
 

        

        
~
Class Sample
    ' メッセージを表示するメソッド
~
  Shared Sub Main()
    Sub OutputMessage(ByVal msg As String)
+
    Using fromStream As FileStream = File.OpenRead("source.txt")
+
      Using toStream As FileStream = File.OpenWrite("dest.txt")
+
        fromStream.CopyTo(toStream)
+
      End Using
+
    End Using
+
  End Sub
+
End Class
+
}}
+
#tabpage-end
 

        

        
~
***usingステートメントとIDisposableを使った終了処理のラップ
        Debug.WriteLine("Ticks " + DateTime.Now.Ticks.ToString() + ": " + msg)
+
usingステートメントと終了処理をラップしたIDisposableを組み合わせて使うことにより、終了処理の記述をシンプルで確実なものにすることが出来ます。 例として、BeginXXX/EndXXXやOpenXXX/CloseXXXのように必ずペアで呼び出す必要があるメソッドがあります。 このようなメソッドの組み合わせをラップしたIDisposableを実装し、usingステートメントと組み合わせて使うことでtry-finallyよりも簡単に記述できるようになります。
 

        

        
~
以下の例は、Bitmapクラスの&msdn(netfx,method,System.Drawing.Bitmap.LockBits){LockBits};/&msdn(netfx,method,System.Drawing.Bitmap.UnlockBits){UnlockBits};メソッドの呼び出しをラップするクラスBitmapLockを作成し、usingステートメントで使用できるようにしたものです。 この例では、画像ファイルorigin.pngを読み込み、色を反転した結果をnegated.pngとして保存しています。
    End Sub
 

        

        
~
#tabpage(C#)
End Module
+
#code(cs){{
+
using System;
+
using System.Drawing;
+
using System.Drawing.Imaging;
+
using System.Runtime.InteropServices;
+

          
+
// Bitmap.LockBits/UnlockBitsの処理をラップしたクラス
+
class BitmapLock : IDisposable
+
{
+
  private Bitmap bitmap;
+
  private BitmapData data;
+

          
+
  public BitmapLock(Bitmap bitmap, Rectangle rect, ImageLockMode mode, PixelFormat format)
+
  {
+
    this.bitmap = bitmap;
+
    this.data = bitmap.LockBits(rect, mode, format);
+
  }
+

          
+
  public void Dispose()
+
  {
+
    if (data != null)
+
    {
+
      bitmap.UnlockBits(data);
+

          
+
      data = null;
+
      bitmap = null;
+
    }
+
  }
+

          
+
  // ラインyの先頭のポインタを取得する
+
  public IntPtr GetScanLine(int y)
+
  {
+
    if (data == null) throw new ObjectDisposedException(GetType().FullName);
+

          
+
    return new IntPtr(data.Scan0.ToInt32() + y * data.Stride);
+
  }
+
}
+

          
+
class Sample
+
{
+
  static void Main()
+
  {
+
    using (Bitmap bitmap = (Bitmap)Image.FromFile("origin.png"))
+
    {
+
      Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
+

          
+
      // 画像全体のピクセルを読み書きできるようにロックする
+
      using (BitmapLock locked = new BitmapLock(bitmap, rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb))
+
      {
+
        for (int y = 0; y < rect.Height; y++)
+
        {
+
          IntPtr line = locked.GetScanLine(y);
+

          
+
          for (int x = 0; x < rect.Width; x++)
+
          {
+
            // 1ピクセル分読み込み
+
            int col = Marshal.ReadInt32(line, x * 4);
+

          
+
            // 反転した値を書き込む
+
            Marshal.WriteInt32(line, x * 4, ~col);
+
          }
+
        }
+
      }
+

          
+
      bitmap.Save("negated.png", ImageFormat.Png);
+
    }
+
  }
+
}
 
}}
}}
+
#tabpage(VB)
+
#code(vb){{
+
Imports System
+
Imports System.Drawing
+
Imports System.Drawing.Imaging
+
Imports System.Runtime.InteropServices
+

          
+
' Bitmap.LockBits/UnlockBitsの処理をラップしたクラス
+
Class BitmapLock
+
  Implements IDisposable
+

          
+
  Private bitmap As Bitmap
+
  Private data As BitmapData
+

          
+
  Public Sub New(ByVal bitmap As Bitmap, ByVal rect As Rectangle, ByVal mode As ImageLockMode, ByVal format As PixelFormat)
+
    MyClass.bitmap = bitmap
+
    MyClass.data = bitmap.LockBits(rect, mode, format)
+
  End SUb
+

          
+
  Public Sub Dispose() Implements IDisposable.Dispose
+
    If Not data is Nothing Then
+
      bitmap.UnlockBits(data)
+

          
+
      data = Nothing
+
      bitmap = Nothing
+
    End If
+
  End Sub
+

          
+
  ' ラインyの先頭のポインタを取得する
+
  Public Function GetScanLine(ByVal y As Integer) As IntPtr
+
    If data Is Nothing Then Throw New ObjectDisposedException(Me.GetType().FullName)
 

        

        
~
    Return New IntPtr(data.Scan0.ToInt32() + y * data.Stride)
#prompt(実行結果){{
~
  End Function
Ticks 631850037169421520: アプリケーションが開始されました。
~
End Class
Ticks 631850037181639088: インスタンスが作成されます。
-
Ticks 631850037181739232: コンストラクタが呼び出されました
-
Ticks 631850037181739232: インスタンスが作成されました。
-
Ticks 631850037181739232: アプリケーションが終了されます。
-
Ticks 631850037181839376: Dispose()メソッドが呼び出されました。
-
}}
 

        

        
~
Class Sample
このコードのように実装しておけば、Dispose()を呼び出せば明示的にリソースを解放でき、また万が一Dispose()を呼び出すことを忘れても解放されないままになるなどの深刻な事態にはならなくなります。
+
  Shared Sub Main()
+
    Using bitmap As Bitmap = DirectCast(Image.FromFile("origin.png"), Bitmap)
+
      Dim rect As New Rectangle(0, 0, bitmap.Width, bitmap.Height)
+

          
+
      ' 画像全体のピクセルを読み書きできるようにロックする
+
      Using locked As New BitmapLock(bitmap, rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
+
        For y As Integer = 0 To rect.Height - 1
+
          Dim line As IntPtr = locked.GetScanLine(y)
+

          
+
          For x As Integer = 0 To rect.Width - 1
+
            ' 1ピクセル分読み込み
+
            Dim col As Integer = Marshal.ReadInt32(line, x * 4)
+

          
+
            ' 反転した値を書き込む
+
            Marshal.WriteInt32(line, x * 4, Not col)
+
          Next
+
        Next
+
      End Using
+

          
+
      bitmap.Save("negated.png", ImageFormat.Png)
+
    End Using
+
  End Sub
+
End Class
+
}}
+
#tabpage-end