web-dev-qa-db-ja.com

継承された関数が基本型ではなく派生型を返す

私はC#で2つのクラスを書いています:

  • N行m列の次元を持つ一般的なMatrixを表すMatrixクラス
  • SquareMatrixを継承し、n行n列の制約があるMatrixクラス

このように設計した理由は、正方行列が行列式や逆行列の計算などの追加の特定の演算をサポートしているため、使用している特定の型でこれらの関数が使用可能であることを保証できるのは素晴らしいことです。さらに、すべての通常のマトリックス操作をサポートし、マトリックスとして使用できます

MatrixgetTranspose()という関数があります。行列の転置を計算し、新しいMatrixとして返します

私はそれをSquareMatrixで継承しましたが、正方行列の転置は正方行列であることが保証されているため、SquareMatrixも返します

これを行うための最良の方法がわかりません。

  • SquareMatrixで関数を再実装できますが、基本的に同じ計算であるため、コードの重複になります
  • 暗黙の型キャスト演算子を使用できますが、不必要な割り当てが発生することを正しく理解している場合(SquareMatrixMatrixにアップキャストし、転置として新しいMatrixを作成し、型キャスト中に新しいSquareMatrixを作成し、転置されたMatrixを破棄します)
  • 私は明示的な型キャスト演算子を使用できますが、SquareMatrixの転置を明示的に型キャストする必要があるのはばかげているでしょう

別のオプションはありますか? SquareMatrixMatrixから継承するようにデザインを変更する必要がありますか?

この問題はオペレーターにも当てはまります。パフォーマンスが低下する可能性のある型キャスト演算子を実装するか、同じコードを再実装する必要があるようです。

6
9a3eedi

継承が繰り返しや型キャストの排除に役立たないことは、ジェネリックスが役立つ兆候であることがよくあります。次のようなことができます:

_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;
}
_

これには、SquareMatrixgetTranspose()の実装がまったく必要ないという利点がありますが、呼び出しサイトで戻り値の型チェックが必要になるという犠牲が伴います。また、たまたま正方形の結果をもたらす2つの非正方形の行列を乗算するような場合にも機能します。ただし、ほとんどのコンパイル時の型チェックはあきらめます。

とにかく、アプリケーションがコンパイル時の型チェックではなく実行時をほとんど必要とする場合、非正方行列がサポートしていないメソッドを呼び出すと、さまざまな型を放棄して例外をスローすることもできます。特にinverse()などのメソッドが失敗する原因となる可能性がある非正方形以外の条件があるため、これはほとんどの既存のライブラリが採用しているアプローチだと思います。

ライブラリと言えば、行列の計算に適したライブラリがたくさんありますが、それらはすでに十分にテストおよび最適化されています。必要がない場合は、ホイールを再発明しないでください。

6
Karl Bielefeldt

問題は、「このタイプ」の概念がC#にないことです。シミュレーションはできますが、構文が少し複雑またはわかりにくいため、使用しないことをお勧めします。以下の質問は、そのような実装について説明しています。

https://stackoverflow.com/questions/1400831/is-it-possible-to-make-this-type-for-generics-in-c

2
Florian F

継承された関数で「new」演算子を使用して出力をオーバーライドし、「base」演算子を使用して基本クラスを呼び出します。

何かのようなもの:

public new SquareMatrix getTranspose()
{
    // Do something different here... or not...
    return (SquareMatrix)base.getTranspose();
}
2
Alan Ahlberg

あなたが提案しているキャストのパフォーマンス上のペナルティは実際のものではなく、ベースタイプを派生型にキャストした場合、何も再割り当てされません。したがって、受信側でこれを安全に行うことができます。クライアントコードは、派生クラスを作成したため、よりリッチな型が確実に得られることがわかっている場合、できれば "as"演算子を使用して、返された型をよりリッチな型にキャストできます。これは決して悪い習慣ではありません。

クライアントコードのキャストに本当にバグがある場合は、派生クラスにメソッドを追加することができます(名前は少し異なります)。提案するように何も再実装する必要はありません。base.GetTransposeを呼び出して、戻る前にキャストを実行します。

T型は、処理されるバリアント型ではなく、実装されるクラスである必要があるため、ジェネリックスはここではあまり役に立ちません。不可能だよ。

アランの提案はハックであり、それは子孫のためのメソッドの仮想性を殺すでしょう。

しかし、ここがキッカーです。制約を実装するために派生クラスを使用するというあなたの考えは、明らかに間違っています。これはLSPに違反しており、問題の根本的な原因です。代わりに、基本クラスにIsSquareプロパティが必要です。

1
Martin Maat