クラスの機能をテストするために .NET で使用されるかなり一般的なパターンがあります。ここでは例として Stream クラスを使用しますが、問題はこのパターンを使用するすべてのクラスに当てはまります。
このパターンは、機能 XXX がクラスで使用可能であることを示すために、CanXXX と呼ばれるブール値のプロパティを提供することです。たとえば、Stream クラスには、Read、Write、および Seek メソッドを呼び出すことができることを示す CanRead、CanWrite、および CanSeek プロパティがあります。プロパティの値が false の場合、それぞれのメソッドを呼び出すと、NotSupportedException がスローされます。
ストリーム クラスに関する MSDN ドキュメントから:
基になるデータ ソースまたはリポジトリによっては、ストリームがこれらの機能の一部しかサポートしない場合があります。アプリケーションは、CanRead、CanWrite、および CanSeek プロパティを使用して、ストリームの機能を照会できます。
CanRead プロパティのドキュメント:
派生クラスでオーバーライドされると、現在のストリームが読み取りをサポートしているかどうかを示す値を取得します。
Stream から派生したクラスが読み取りをサポートしていない場合、Read、ReadByte、および BeginRead メソッドを呼び出すと、NotSupportedException がスローされます。
次の行に沿って書かれたコードがたくさんあります。
if (stream.CanRead)
{
stream.Read(…)
}
たとえば、ストリーム オブジェクトを何らかの方法でロックするための同期コードがないことに注意してください。他のスレッドがストリーム オブジェクトまたはそれが参照するオブジェクトにアクセスしている可能性があります。NotSupportedException をキャッチするコードもありません。
MSDN のドキュメントには、プロパティ値が時間の経過とともに変更できないとは記載されていません。実際、ストリームが閉じられると CanSeek プロパティは false に変化し、これらのプロパティの動的な性質を示しています。そのため、上記のコード スニペットの Read() の呼び出しが NotSupportedException をスローしないという契約上の保証はありません。
この潜在的な問題に悩まされているコードがたくさんあると思います。この問題を特定した人たちは、どのように対処したのだろうか。ここで適切な設計パターンは何ですか?
また、このパターン (CanXXX、XXX() のペア) の妥当性についてコメントをいただければ幸いです。私にとって、少なくとも Stream クラスの場合、これは多すぎることをしようとしているクラス/インターフェースを表し、より基本的な部分に分割する必要があります。厳密で文書化された契約がないため、テストが不可能になり、実装がさらに難しくなります!