ジェネリックの芸術

ユニバーサルテンプレート-ジェネリックでもあり、最も強力な開発ツールの1つです。



CLRは、MSILレベルでそれらをサポートし、ランタイム全体をサポートします。これにより、タイプセーフなトリックを実行できます。



C ++テンプレートに精通しているが、コンパイル段階でコンパイルしない場合、それを上げたい場合は、猶予によってC#操作に決して劣らないので、この記事はこれに役立ちます。



patternsパターンについて少し



コードの編成をより便利にするため、およびプログラミングパターンの開発におけるOOPの使用は、通常一緒に使用されます。



[ 注:以下の例は、この記事の主な目的であるジェネリックの「トリック」とは特に関係ありません。 著者は思考の列を見せたいだけです。]



MVCの価値は何ですか。 ここで、コントローラー側のロジックを処理するために、 ストラテジーを使用するか、 ファクトリーメソッドと一緒に使用することができます( 抽象ファクトリーと混同しないでください)。



それらを記述するのはGoFよりも良いので、先に進みましょう。



次のようなパターンがあります。



最初の本質は、単一のディスパッチを拡張することです-オブジェクトのタイプによってオーバーロードします。



たとえば、C#4とそのダイナミックから始めて、 ウィキペディアから簡単に例を表示できます。



複数発送
class Program { class Thing { } class Asteroid : Thing { } class Spaceship : Thing { } static void CollideWithImpl(Asteroid x, Asteroid y) { Console.WriteLine("Asteroid collides with Asteroid"); } static void CollideWithImpl(Asteroid x, Spaceship y) { Console.WriteLine("Asteroid collides with Spaceship"); } static void CollideWithImpl(Spaceship x, Asteroid y) { Console.WriteLine("Spaceship collides with Asteroid"); } static void CollideWithImpl(Spaceship x, Spaceship y) { Console.WriteLine("Spaceship collides with Spaceship"); } static void CollideWith(Thing x, Thing y) { dynamic a = x; dynamic b = y; CollideWithImpl(a, b); } static void Main(string[] args) { var asteroid = new Asteroid(); var spaceship = new Spaceship(); CollideWith(asteroid, spaceship); CollideWith(spaceship, spaceship); } }
      
      







ご覧のとおり、単純なメソッドのオーバーロードでは、このパターンを実装するのに十分ではありません。

しかし、今度はDoubleディスパッチに進みましょう。 この方法で例を書き換えます。



ダブルディスパッチ
 class Program { interface ICollidable { void CollideWith(ICollidable other); } class Asteroid : ICollidable { public void CollideWith(Asteroid other) { Console.WriteLine("Asteroid collides with Asteroid"); } public void CollideWith(Spaceship spaceship) { Console.WriteLine("Asteroid collides with Spaceship"); } public void CollideWith(ICollidable other) { other.CollideWith(this); } } class Spaceship : ICollidable { public void CollideWith(ICollidable other) { other.CollideWith(this); } public void CollideWith(Asteroid asteroid) { Console.WriteLine("Spaceship collides with Asteroid"); } public void CollideWith(Spaceship spaceship) { Console.WriteLine("Spaceship collides with Spaceship"); } } static void Main(string[] args) { var asteroid = new Asteroid(); var spaceship = new Spaceship(); asteroid.CollideWith(spaceship); asteroid.CollideWith(asteroid); } }
      
      







ご覧のとおり、ダイナミックなしで実行できます。



なぜこれすべてなのか?



答えは簡単です。オブジェクトの種類によってオーバーロードする単一のディスパッチを展開し、複数のオブジェクトをオーバーロードする場合( 複数のディスパッチ )に拡張できる場合は、ジェネリックも使用してみませんか?!



共分散&& 分散



一般に、プログラミング言語の型の共分散は当たり前のように思われます。 例:



 var asteroid = new Asteroid(); ICollidable collidable = asteroid;
      
      





ただし、これは割り当ての互換性と呼ばれます。



ジェネリックを使用すると、共分散が正確に現れます。



 List<Asteroid> asteroids = new List<Asteroid>(); IEnumerable<ICollidable> collidables = asteroids;
      
      





IEnumerable宣言は次のとおりです。



 public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }
      
      





outキーワードと共分散のサポートがない場合、List <T>クラスがこのインターフェイスを実装しているにもかかわらず、List <Asteroid>型をIEnumerable <ICollidable>型にキャストすることはできません。



Tとマークれた型は、別のクラスまたはインターフェイスへの型付き引数としても、メソッドパラメーターとして使用できないことを既にご存じでしょう。 例:



 interface ICustomInterface<out T> { T Do(T target); //compile-time error T Do(IList<T> targets); //compile-time error }
      
      





さて、この機能をメモしてみましょうが、今のところは目標に進みましょう-ジェネリックでオーバーロードする可能性を拡大します。



ジェネリックのコンパイル時チェック



次のインターフェースを検討してください。



 public interface IReader<T> { T Read(T[] arr, int index); }
      
      





一見異常なことはありません。 しかし、数値または浮動小数点数のみの実装を実装する方法は? つまり コンパイル時に型制限を導入しますか?



C#はそのような機会を提供しません。 型付きパラメーターの構造体、クラス、または特定の型(まだ新しい())としてのみ指定できます。



 public interface IReader<T> where T : class { T Read(T[] arr, int index); }
      
      





複数のディスパッチの小惑星の例を覚えていますか?



IReaderの実装にも同じものを使用します。



 public class SignedIntegersReader : IReader<Int32>, IReader<Int16>, IReader<Int64> { int IReader<int>.Read(int[] arr, int index) { return arr[index]; } short IReader<short>.Read(short[] arr, int index) { return arr[index]; } long IReader<long>.Read(long[] arr, int index) { return arr[index]; } }
      
      





私は疑問が生じると思います-なぜインターフェイスの明示的な実装が正確なのですか?



すべてのインターフェイスメソッドで共分散をサポートすることがすべてです。



そのため、共変インターフェイスには、たとえばIListであっても、メソッドにタイプTのパラメーターを含めることはできません。



また、C#では戻り値型によるメソッドのオーバーロードのサポートが不可能であるため、引数の数がゼロ以上のメソッドを持つインターフェイスの複数の暗黙的な実装はコンパイルされません。



さて、実際にこれらの機会を使用することは残っています。



 public static class ReaderExtensions { public static T Read<TReader, T>(this TReader reader, T[] arr, int index) where TReader : IReader<T> { return reader.Read(arr, index); } } class Program { static void Main(string[] args) { var reader = new SignedIntegersReader(); var arr = new int[] {128, 256}; for (int i = 0; i < arr.Length; i++) { Console.WriteLine("Reader result: {0}", reader.Read(arr, i)); } } }
      
      





変数arrの型をfloat []に変更してみましょう。



 class Program { static void Main(string[] args) { var reader = new SignedIntegersReader(); var arr = new float[] {128.0f, 256.0f}; for (int i = 0; i < arr.Length; i++) { Console.WriteLine("Reader result: {0}", reader.Read(arr, i)); //compile-time error } } }
      
      





しかし、これは拡張メソッドによってのみ達成されますか?! インターフェイスの実装が必要な場合はどうなりますか?



IReaderインターフェイスを少し変更します。



IReader <T>
 public interface IReader<T> { T Read(T[] arr, int index); bool Supports<TType>(); } public class SignedIntegersReader : IReader<Int32>, IReader<Int16>, IReader<Int64> { int IReader<int>.Read(int[] arr, int index) { return arr[index]; } short IReader<short>.Read(short[] arr, int index) { return arr[index]; } long IReader<long>.Read(long[] arr, int index) { return arr[index]; } public bool Supports<TType>() { return this as IReader<TType> != null; } }
      
      







IReaderの別の実装であるDefaultReaderを追加します。



 public class DefaultReader<T> : IReader<T> { private IReader<T> _reader = new SignedIntegersReader() as IReader<T>; public T Read(T[] arr, int index) { if (_reader != null) { return _reader.Read(arr, index); } return default(T); } public bool Supports<TType>() { return _reader.Supports<TType>(); } }
      
      





実際に確認してください:



 class Program { static void Main(string[] args) { var reader = new DefaultReader<int>(); var arr = new int[] { 128, 256 }; if (reader.Supports<int>()) { for (int i = 0; i < arr.Length; i++) { Console.WriteLine("Reader result: {0}", reader.Read(arr, i)); } } } }
      
      





したがって、パラメーター化された型によるオーバーロードをチェックするタスクの2つの実装(コンパイル時と実行時の両方)を取得しました。




All Articles