ICloneable<T>
(修正:IClonable→ICloneable)
MSDN Forums での投稿用に作ってみたものを紹介.
.NET 2.0 になって従来のインターフェイスを補完する generic interfaces がいくつか追加されていますが,ICloneable に対する ICloneable
というわけで作ってみたわけですがこれです.
http://www.dwahan.net/nyaruru/hatena/variant.zip
折角なので MSIL を使って ICloneable<+T> として作りました*1.これにより例えば ICloneable
んでは実際に使ってみましょう.
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
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
最後に呼び出し部分.
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 参照のこと