私はC#で2つのクラスを書いています:
Matrix
クラスSquareMatrix
を継承し、n行n列の制約があるMatrix
クラスこのように設計した理由は、正方行列が行列式や逆行列の計算などの追加の特定の演算をサポートしているため、使用している特定の型でこれらの関数が使用可能であることを保証できるのは素晴らしいことです。さらに、すべての通常のマトリックス操作をサポートし、マトリックスとして使用できます
Matrix
にgetTranspose()
という関数があります。行列の転置を計算し、新しいMatrix
として返します
私はそれをSquareMatrix
で継承しましたが、正方行列の転置は正方行列であることが保証されているため、SquareMatrix
も返します
これを行うための最良の方法がわかりません。
SquareMatrix
で関数を再実装できますが、基本的に同じ計算であるため、コードの重複になりますSquareMatrix
をMatrix
にアップキャストし、転置として新しいMatrix
を作成し、型キャスト中に新しいSquareMatrix
を作成し、転置されたMatrix
を破棄します)SquareMatrix
の転置を明示的に型キャストする必要があるのはばかげているでしょう別のオプションはありますか? SquareMatrix
がMatrix
から継承するようにデザインを変更する必要がありますか?
この問題はオペレーターにも当てはまります。パフォーマンスが低下する可能性のある型キャスト演算子を実装するか、同じコードを再実装する必要があるようです。
継承が繰り返しや型キャストの排除に役立たないことは、ジェネリックスが役立つ兆候であることがよくあります。次のようなことができます:
_public T getTranspose<T>()
// or non-member function
T getTranspose<T>(T input)
_
私はそれを完全には解決していませんが、呼び出し側でぎこちないかもしれません。 C#がジェネリックメソッドと何らかの推論を行うことは知っていますが、C#がわからないため、詳細に精通していません。ただし、実装時の繰り返し回数を最小限に抑えて完全なコンパイル時の型チェックを行う場合は、この方法を使用する必要があります。
別のオプションは、プライベートヘルパー関数を作成してから、ヘルパーに入力するために、次のように必要な結果タイプを渡すことです。
_public SquareMatrix getTranspose() {
SquareMatrix result = new SquareMatrix();
transposeHelper(result);
return result;
}
_
これにより、実装側のボイラープレートが増えますが、少なくとも完全な繰り返しではありません。
3番目のオプションは、結果がMatrix
実装で正方形であるかどうかを確認し、正方形の場合はSquareMatrix
を返すことです。
_public Matrix getTranspose() {
Matrix result;
if (resultIsSquare())
result = new SquareMatrix();
else
result = new Matrix();
// calculate result
return result;
}
_
これには、SquareMatrix
のgetTranspose()
の実装がまったく必要ないという利点がありますが、呼び出しサイトで戻り値の型チェックが必要になるという犠牲が伴います。また、たまたま正方形の結果をもたらす2つの非正方形の行列を乗算するような場合にも機能します。ただし、ほとんどのコンパイル時の型チェックはあきらめます。
とにかく、アプリケーションがコンパイル時の型チェックではなく実行時をほとんど必要とする場合、非正方行列がサポートしていないメソッドを呼び出すと、さまざまな型を放棄して例外をスローすることもできます。特にinverse()
などのメソッドが失敗する原因となる可能性がある非正方形以外の条件があるため、これはほとんどの既存のライブラリが採用しているアプローチだと思います。
ライブラリと言えば、行列の計算に適したライブラリがたくさんありますが、それらはすでに十分にテストおよび最適化されています。必要がない場合は、ホイールを再発明しないでください。
問題は、「このタイプ」の概念がC#にないことです。シミュレーションはできますが、構文が少し複雑またはわかりにくいため、使用しないことをお勧めします。以下の質問は、そのような実装について説明しています。
https://stackoverflow.com/questions/1400831/is-it-possible-to-make-this-type-for-generics-in-c
継承された関数で「new」演算子を使用して出力をオーバーライドし、「base」演算子を使用して基本クラスを呼び出します。
何かのようなもの:
public new SquareMatrix getTranspose()
{
// Do something different here... or not...
return (SquareMatrix)base.getTranspose();
}
あなたが提案しているキャストのパフォーマンス上のペナルティは実際のものではなく、ベースタイプを派生型にキャストした場合、何も再割り当てされません。したがって、受信側でこれを安全に行うことができます。クライアントコードは、派生クラスを作成したため、よりリッチな型が確実に得られることがわかっている場合、できれば "as"演算子を使用して、返された型をよりリッチな型にキャストできます。これは決して悪い習慣ではありません。
クライアントコードのキャストに本当にバグがある場合は、派生クラスにメソッドを追加することができます(名前は少し異なります)。提案するように何も再実装する必要はありません。base.GetTransposeを呼び出して、戻る前にキャストを実行します。
T型は、処理されるバリアント型ではなく、実装されるクラスである必要があるため、ジェネリックスはここではあまり役に立ちません。不可能だよ。
アランの提案はハックであり、それは子孫のためのメソッドの仮想性を殺すでしょう。
しかし、ここがキッカーです。制約を実装するために派生クラスを使用するというあなたの考えは、明らかに間違っています。これはLSPに違反しており、問題の根本的な原因です。代わりに、基本クラスにIsSquareプロパティが必要です。