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


24

私には大きなオブジェクトがあります:

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

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

PSこれが関連するかどうかはわかりませんが、異なる設定にOtherBigObject変換することもできるでしょう。SmallObjectEnumType


4
なぜコンストラクターではないのですか?
edc65

2
または静的なファクトリメソッドですか?
ブライアンゴードン

なぜファクトリークラス、または依存性注入が必要なのですか?あなたはそこで間違った二分法を作りました。
user253751

1
@immibis-@Telastynが提案した.ToSmallObject()方法(メソッドGetSmallObject())についてどうも考えなかったため。瞬間的な理由の失効-私は自分の思考に何か問題があることを知っていたので、皆さんに尋ねました:)
ジェリーノ

3
これは、ISmallObjectインターフェースの完全なユースケースのように聞こえます。ISmallObjectインターフェースは、BigObjectによって、その広範なデータ/動作の限られたセットへのアクセスを提供する手段としてのみ実装されています。特に、@ TelastynのToSmallObjectメソッドのアイデアと組み合わせると。
マルジャンヴェネマ

回答:


0

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


これは素晴らしいアイデアです。すでにAutomapperが用意されているので、簡単です。私が持っている唯一の問題:BigObjectとSmallObjectが何らかの形で関連しているという痕跡はないはずですか?
ジェリーノ

1
いいえ、マッピングサービス以外にBigObjectとSmallObjectをさらに結合することによる利点はありません。
エスベンスコフペダーセン

7
本当に?オートマッパーは設計上の問題の解決策ですか?
テラスティン

1
BigObjectはSmallObjectにマッピング可能であり、古典的なOOPの意味では実際には相互に関連しておらず、コードはこれを反映しています(両方のオブジェクトがドメインに存在し、マッピング機能は他の多くのマッピング設定とともに設定されます)。それは疑わしいコードを削除し(不幸なオペレーターのオーバーライド)、モデルをクリーンなままにします(メソッドはありません)。そう、それは解決策のようです。
ジェリーノ

2
@EsbenSkovPedersenこのソリューションは、ブルドーザーを使用してメールボックスをインストールするための穴を掘るようなものです。幸いなことに、OPはとにかく庭を掘りたいと思ったので、この場合はブルドーザーが動作します。ただし、一般的にこのソリューションはお勧めしません。
ニール

81

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

.ToSmallObject()代わりに方法をお勧めします。実際に何が起こっているのか、および冗長であるのかについては、より明確です。


18
Doh ... ToSmallObject()は最も明白な選択のようです。時々、最も明白なのは最もとらえどころのないです;)
ジェリーノ

6
mildly distasteful控えめな表現です。言語がこの種のものを型キャストのように見えることを許可しているのは残念です。自分で書いていない限り、実際のオブジェクト変換だとは誰も思いません。一人のチームでは、結構です。誰かとコラボレーションする場合、それが本当にキャストなのか、それとも変な変身の1つなのかを突き止めなければならないので、最良の場合には時間の無駄です。
ケントA.

3
@Telastynは、最もひどいコードの匂いではないことに同意しました。しかし、ほとんどのプログラマーが同じオブジェクトを異なるタイプとして扱うためのコンパイラーへの命令であると理解している操作からの新しいオブジェクトの隠された作成は、あなたのコードで作業しなければならない人には不親切です。:)
ケントA.

4
+1 .ToSmallObject()。演算子をオーバーライドする必要はほとんどありません。
イトレダーノ

6
@dorus-少なくとも.NETではGet、既存のものを返すことを意味します。小さいオブジェクトの操作をオーバーライドしない限り、2つのGet呼び出しは等しくないオブジェクトを返し、混乱/バグ/ wtfsを引き起こします。
テラスティン

11

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

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

注意してください、これは(私の謙虚な意見にあるように)にBigObject依存するのではなくSmallObject、むしろその逆です。


ファサードが新しいクラスへのフィールドのコピーよりも優れていると述べた唯一の利点は、コピーを回避することです(オブジェクトに不合理な量のフィールドがない限り、おそらく問題ではありません)。一方、静的な変換方法とは異なり、新しいクラスに変換する必要があるたびに元のクラスを変更する必要があるという欠点があります。
ドーバル

@Dovalそれがポイントだと思います。新しいクラスに変換することはありません。必要な場合は、別のファサードを作成します。BigObjectに加えられた変更は、Facadeクラスにのみ適用され、使用されるすべての場所に適用される必要はありません。
ニール

このアプローチとTelastynの答えの興味深い違いの1つは、生成の責任SmallObjectSmallObjectまたはにあるかどうかBigObjectです。デフォルトでは、このアプローチSmallObjectはのプライベート/保護されたメンバーへの依存を回避することを強制 しBigObjectます。さらに一歩進んでSmallObjectToSmallObject拡張メソッドを使用することにより、プライベート/保護されたメンバーへの依存を回避できます。
ブライアン

@ブライアンあなたはBigObjectそのように散らかる危険があります。同様のことをしたい場合は、ToAnotherObject内で拡張メソッドを作成することを保証しますBigObjectか?BigObjectおそらく、それは既に十分に大きいので、これらは心配するべきではありません。またBigObject、依存関係の作成から分離できるため、ファクトリーなどを使用できます。他のアプローチは強く結合BigObjectSmallObjectます。この特定のケースではそれで問題ありませんが、私の謙虚な意見ではベストプラクティスではありません。
ニール

1
@Neil実際には、ブライアンはそれを間違って説明しましたが、彼正しいです-拡張メソッドは結合を取り除きます。にBigObject結合されなくなりSmallObject、の引数を取り、BigObjectを返す静的メソッドになりますSmallObject。拡張メソッドは、静的メソッドをより良い方法で呼び出すための単なる構文上のシュガーです。拡張メソッドはの一部ではなくBigObject、完全に独立した静的メソッドです。これは実際には拡張メソッドの非常に優れた使用方法であり、特にDTO変換に非常に便利です。
ルアーン

6

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

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

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


1

大丈夫かもしれません。

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

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

さて、longintの意味がよくわかったら、intto の暗黙のキャストとto longの明示的なキャストの両方を理解するlongことintができます。また、がどのように3なり3、で作業する別の方法であるかが理解できます3int.MaxValue + 1チェックされたコンテキストでこれがどのように失敗するかは理解できます。int.MaxValue + 1未確認のコンテキストでどのように動作するかでさえint.MinValue、最も難しいことではありません。

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

現在、BigObjectSmallObject使用すると、この関係がどのように機能するのかがわかりません。キャストの関係が明らかな場合、実際のタイプが本当に良い場合、多くの場合、おそらく大多数ですが、キャストは良い考えかもしれません。その場合、クラス階層と通常の継承ベースのキャストで十分です。


実際には、それらは問題で提供されているものよりもはるかに多くはありませんが、たとえば、をBigObject記述したりEmployee {Name, Vacation Days, Bank details, Access to different building floors etc.}、であったりしSmallObjectますMoneyTransferRecepient {Name, Bank details}。からEmployeeへの直接的な変換がありMoneyTransferRecepient、必要以上のデータを銀行業務アプリケーションに送信する理由はありません。
ジェリーノ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.