読者です 読者をやめる 読者になる 読者になる

ICloneable<T>

.NET

(修正:IClonable→ICloneable)
MSDN Forums での投稿用に作ってみたものを紹介.
.NET 2.0 になって従来のインターフェイスを補完する generic interfaces がいくつか追加されていますが,ICloneable に対する ICloneable は存在しません.そのため例えば「複製後のオブジェクトが MemoryStream 型と互換するもの」という表現は標準では出来ないこととなります.
というわけで作ってみたわけですがこれです.
http://www.dwahan.net/nyaruru/hatena/variant.zip
折角なので MSIL を使って ICloneable<+T> として作りました*1.これにより例えば ICloneable を実装するオブジェクトは自動的に ICloneable にキャスト出来ることが保証されます.ただし,C#コンパイラは generic interfaces/delegates に関する covariance/contra-variance の影響を知りませんので,所々で明示的なキャストが必要になっています.
んでは実際に使ってみましょう.

using System;
using System.Collections.Generic;
using System.IO;

class MyClass1 : MemoryStream, Variant.ICloneable<MyClass1>
{
    public MyClass1()
        : base()
    {
    }
    public MyClass1(byte[] buffer, bool writable)
        : base(buffer, writable)
    {
    }
    /// <summary>
    /// オブジェクトの複製(書換え不可)を返す
    /// </summary>
    /// <returns>複製されたMemoryStream (readonly)</returns>
    public MyClass1 Clone()
    {
        return new MyClass1(this.ToArray(), false);
    }
    object ICloneable.Clone()
    {
        return this.Clone();
    }
}

今から定義する型 MyClass1 がコロンの右側に出てくるあたりに ATL を思い出しますね.ポイントはこれだけで MyClass1 は Variant.ICloneable と Variant.ICloneable にキャスト可能になるということです.
ついでに Variant.ICloneable に関するユーティリティクラスも作りましょう.

static class Util
{
    /// <summary>
    /// 複製可能なオブジェクトの列を取り複製したものを1つづつ返す
    /// </summary>
    /// <param name="source">複製可能なオブジェクトの列</param>
    /// <returns>複製されたオブジェクト</returns>
    public static IEnumerable<T>
        DuplicateAll<T>(IEnumerable<Variant.ICloneable<T>> source) 
    {
        foreach (Variant.ICloneable<T> s in source)
        {
            yield return s.Clone();
        }
    }
}

C++ と同じように generic method であればコンパイラが適当に型パラメータを推論してくれることに注意しましょう.これは呼び出し時に違いを生みます.すなわち,Util.DuplicateAll(x) と Util.DuplicateAll(x) では,前者のは常に省略不可ですが,後者のコンパイラが推論できる場合は省略可能という違いがあります.
最後に呼び出し部分.

static class Test
{
    static void Main(string[] args)
    {
        MyClass1 c = new MyClass1();
        c.WriteByte(12);

        List<Variant.ICloneable<MyClass1>> list1
            = new List<Variant.ICloneable<MyClass1>>();
        List<Variant.ICloneable<Stream>> list2
            = new List<Variant.ICloneable<Stream>>();
        List<Variant.ICloneable<object>> list3
            = new List<Variant.ICloneable<object>>();

        // これは普通にOK
        list1.Add(c);

        // 以下の型変換は C# の built-in conversion からは
        // 成功することが推測できないため,
        // コンパイルするには明示的にキャストする必要がある
        list2.Add(c as Variant.ICloneable<Stream>);
        list3.Add(c as Variant.ICloneable<object>);
        
        // T = MyClass1 は型推論により明示不要
        foreach (MyClass1 s in Util.DuplicateAll(list1))
        {
            Console.WriteLine("length : {0}", s.Length);
        }

        // T = Stream は型推論により明示不要
        foreach (Stream s in Util.DuplicateAll(list2))
        {
            Console.WriteLine("length : {0}", s.Length);
        }

        // T = object は型推論により明示不要
        foreach (object s in Util.DuplicateAll(list3))
        {
        }

        //変換したいだけならこうも書ける
        List<MyClass1> output = list1.ConvertAll<MyClass1>(
            delegate (Variant.ICloneable<MyClass1> x)
            {
                return x.Clone();
            }
        );
    }
}

list2 と list3 に Add するところですが,そのままではコンパイルエラーになります.C# コンパイラはこの型変換が (CLR 的には) 常に成功することを知りません.そのため明示的に as で型変換を行ってコンパイラを黙らせています.

*1:詳しくは id:NyaRuRu:20051115#p1 参照のこと