web-dev-qa-db-ja.com

明示的なキャスト演算子の使用は妥当ですか、それともハックですか?

私は大きな目的を持っています:

_class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}
_

また、DTOのよ​​うな特殊なオブジェクト:

_class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}
_

私は個人的にBigObjectを明示的にSmallObjectにキャストするという概念を見つけました-これは一方向のデータ損失操作であることを知っています-非常に直感的で読みやすいです:

_var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);
_

明示的な演算子を使用して実装されます:

_public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}
_

これで、FromBigObject(BigObject big)メソッドを使用してSmallObjectFactoryクラスを作成できます。これにより、同じことを行い、依存関係注入に追加して、必要に応じて呼び出すことができます...しかし、私にはさらに多くのようです複雑すぎて不要です。

PSこれが適切かどうかはわかりませんが、OtherBigObjectがあり、これをSmallObjectに変換して別のEnumTypeに設定することもできます。

25
Gerino

他の答えはどれも私の謙虚な意見では正しくありません。この stackoverflowの質問 で最も高い投票の回答は、マッピングコードをドメインの外に置く必要があると主張しています。あなたの質問に答えるには、いいえ-キャスト演算子の使い方はあまりよくありません。 DTOとドメインオブジェクトの間に位置するマッピングサービスを作成することをお勧めします。そうしない場合は、オートマッパーを使用できます。

0

それは...良くない。私はこの巧妙なトリックを実行するコードを使用してきましたが、混乱を招きました。結局のところ、オブジェクトがそれらをキャストするのに十分に関連している場合、BigObjectSmallObject変数に代入するだけでよいと期待するでしょう。ただし、機能しません。型システムに関する限り、それらは無関係であるため、試行するとコンパイラエラーが発生します。また、キャスティングオペレーターが新しいオブジェクトを作成することは、少し不快です。

代わりに.ToSmallObject()メソッドをお勧めします。実際に何が行われているのか、そして冗長であるのはより明確です。

81
Telastyn

SmallObjectが必要な理由はわかりますが、別の方法で問題に取り組みます。このタイプの問題に対する私のアプローチは、 ファサード を使用することです。その唯一の目的は、BigObjectをカプセル化し、特定のメンバーのみを使用可能にすることです。このように、それは同じインスタンス上の新しいインターフェースであり、コピーではありません。もちろん、またコピーを実行したいかもしれませんが、ファサードと組み合わせてその目的のために作成されたメソッドを通じて行うことをお勧めします(たとえばreturn new SmallObject(instance.Clone()))。

Facadeには、他にも多くの利点があります。つまり、プログラムの特定のセクションが、ファサードを介して利用可能にされたメンバーのみを利用できるようにし、知らないはずのものが利用できないことを効果的に保証します。これに加えて、将来のメンテナンスでBigObjectを変更する際の柔軟性が高く、プログラム全体での使用方法をあまり気にする必要がないという大きな利点もあります。何らかの形で古い動作をエミュレートできる限り、SmallObjectを使用する場所でプログラムを変更しなくても、以前と同じようにBigObjectを機能させることができます。

これは、BigObjectSmallObjectに依存せず、逆に(つまり、私の控えめな意見のように)依存することを意味することに注意してください。

11
Neil

可変参照型へのキャストはIDを保持するという非常に強力な規則があります。システムは通常、ソースタイプのオブジェクトを宛先タイプの参照に割り当てることができる状況では、ユーザー定義のキャスト演算子を許可しないため、ユーザー定義のキャスト操作が変更可能な参照に適しているのはごくわずかです。タイプ。

要件として、x=(SomeType)foo;の後にy=(SomeType)foo;が後に続き、両方のキャストが同じオブジェクトに適用されている場合、x.Equals(y)は常にそして永久に問題のオブジェクトが2つのキャスト間で変更された場合でもtrue。このような状況は、たとえば一方には異なるタイプのオブジェクトのペアがあり、それぞれが他方への不変の参照を保持しており、どちらかのオブジェクトをもう一方のタイプにキャストすると、そのペアのインスタンスが返されます。ラップされるオブジェクトのidentitiesが不変であり、同じタイプの2つのラッパーが同じものをラップした場合、それらが等しいと報告する場合、変更可能なオブジェクトのラッパーとして機能するタイプにも適用できます。コレクション。

特定の例では可変クラスを使用していますが、IDの形式は保持していません。そのため、キャスト演算子の適切な使用法ではないことをお勧めします。

6
supercat

大丈夫かもしれません。

あなたの例の問題は、そのような例のような名前を使用していることです。検討してください:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

longintの意味がよくわかったら、intからlongへの暗黙のキャストとlongからintへの明示的なキャストはかなり理解できます。 33になる仕組みも理解でき、3を使用するもう1つの方法です。チェックされたコンテキストでこれがint.MaxValue + 1でどのように失敗するかは理解できます。未チェックのコンテキストでint.MaxValue + 1を使用してint.MinValueを生成する方法でさえ、理解するのが最も難しいことではありません。

同様に、暗黙的に基本型または明示的に派生型にキャストすると、継承がどのように機能するか、および結果はどうなるか(または失敗する可能性があるか)を知っている人なら誰でも理解できます。

BigObjectおよびSmallObjectを使用すると、この関係がどのように機能するのかわかりません。キャストの関係が明らかであるような実際の型の場合、キャストは確かに良い考えですが、多くの場合、おそらく大多数の場合、これが当てはまる場合は、クラス階層と通常の継承ベースのキャストで十分です。

1
Jon Hanna