整数識別子を列挙型にマッピングすることの欠点は何ですか?


16

私はこのような識別子のカスタム型を作成することを考えています:

public enum CustomerId : int { /* intentionally empty */ }
public enum OrderId : int { }
public enum ProductId : int { }

これの主な動機は、orderItemIdを予期していた関数に誤ってorderItemIdを渡すようなバグを防ぐことです。

列挙型は、典型的な.NET Webアプリケーションで使用するすべてのものとシームレスに動作するようです。

  • MVCルーティングは正常に動作します
  • JSONシリアル化は正常に動作します
  • 私がうまくいくと思うすべてのORM

だから今、私は「なぜ私はこれをするべきではないのだろうか?これらは私が考えることができる唯一の欠点です。

  • 他の開発者を混乱させる可能性があります
  • 非整数の識別子がある場合、システムに不整合が生じます。
  • のように、追加のキャストが必要になる場合があり(CustomerId)42ます。しかし、ORMおよびMVCルーティングは通常、enum型の値を直接渡すので、これが問題になるとは思いません。

だから私の質問は、何が欠けているのですか?これはおそらく悪い考えですが、なぜですか?



3
これは、あなたの周りグーグルできる「microtyping」と呼ばれている(例えば、この Javaのため、詳細は異なりますが、モチベーションと同じである)
QBD

私はこの間違ったことを考えていることに気付いたので、2つの部分的な回答を入力してから削除しました。「これはおそらく悪い考えですが、なぜですか?」
user2023861 16

回答:


7

主にあなたが述べた理由のために、あらゆる種類の識別子に対してタイプを作成することは素晴らしい考えです。適切な構造体またはクラスにすることでより多くのオプションが提供されるため、列挙型として実装することでそれを行いません:

  • 暗黙的な変換をintに追加できます。考えてみると、それはint-> CustomerIdという別の方向です。これは、消費者からの明示的な確認に値する問題のある方向です。
  • 許可される識別子と許可されない識別子を指定するビジネスルールを追加できます。intが正であるかどうかの弱いチェックだけではありません。場合によっては、可能なすべてのIDのリストがデータベースに保持されますが、展開中にのみ変更されます。次に、適切なクエリのキャッシュされた結果に対して特定のIDが存在することを簡単に確認できます。これにより、無効なデータが存在する場合にアプリケーションが迅速失敗することを保証できます。
  • 識別子のメソッドは、高価なDB存在チェックの実行や、対応するエンティティ/ドメインオブジェクトの取得に役立ちます。

これの実装については、記事Functional C#:Primitive obsessionで読むことができます。


1
私はクラスではなく、構造体の各int型をラップしたくないので、ほぼ確実にあなたを言うだろう
JK。

良い発言、答えを少し更新しました。
ルカシュランスキー16

3

それは賢いハックですが、それでもハックは意図されていない目的のために機能を悪用します。ハックには保守性にコストがかかるため、他の欠点を見つけることができないだけでなく、同じ問題を解決するためのより慣用的な方法と比較して大きな利点が必要です。

慣用的な方法は、単一のフィールドでラッパー型または構造体を作成することです。これにより、より明確で慣用的な方法で同じタイプセーフティが得られます。あなたの提案は確かに賢いですが、ラッパー型を使用することには大きな利点はありません。


1
列挙型の主な利点は、MVC、シリアル化、ORMなどで希望どおりに「機能する」ことです。過去にカスタム型(構造体およびクラス)を作成しており、最終的にあなたよりも多くのコードを書くことになりますこれらのフレームワーク/ライブラリをカスタムタイプで動作させるために必要です。
default.kramer

シリアル化はどちらの方法でも透過的に機能するはずですが、たとえば ORMでは、何らかの明示的な変換を構成する必要があります。しかし、これは強く型付けされた言語の価格です。
ジャックB

2

私のPOVから、アイデアは良いです。関係のないクラスの識別子に異なる型を与え、そうでなければ型を介してセマンティクスを表現し、コンパイラにそれをチェックさせたいという意図は良いものです。

.NETのパフォーマンス面でどのように機能するかわかりません。たとえば、列挙値は必ずしもボックス化されています。とにかくデータベースで動作するOTOHコードはおそらくI / Oにバインドされ、ボックス化された識別子はボトルネックにはなりません。

完璧な世界では、識別子は不変であり、平等に関してのみ匹敵します。実際のバイトは実装の詳細になります。関連のない識別子には互換性のない型があるため、誤って別の識別​​子を使用することはできません。あなたのソリューションは実質的に法案に適合します。


-1

これを行うことはできません。列挙型は事前に定義する必要があります。そのため、顧客IDの可能な値の全範囲を事前に定義しない限り、タイプは役に立たなくなります。

キャストすると、タイプAのIDをタイプBのIDに誤って割り当てることを防ぐという主な目的に反することになります。したがって、列挙型は目的に役立ちません。

ただし、Int32(またはGuid)から派生して、顧客ID、注文ID、および製品IDのタイプを作成すると役立つ場合は、質問が表示されます。これが間違っている理由を考えることができます。

IDの目的は識別です。積分型はすでにこれに最適であり、それらから派生することで識別型を取得することはありません。特殊化は必要ありませんし、望ましいこともありません。識別子に異なるタイプを持たせることで、あなたの世界がより良く表現されることはありません。したがって、オブジェクト指向の観点からは、それは悪いでしょう。

あなたの提案では、型の安全性以上のものが必要であり、価値の安全性が必要であり、それはモデリング領域を超えており、それは運用上の問題です。


1
いいえ、列挙型は.NETで事前に定義する必要はありません。そしてポイントは、キャストがほとんど発生しpublic ActionResult GetCustomer(CustomerId custId)ないことです。たとえば、キャストが不要な場合-MVCフレームワークが値を提供します。
default.kramer

2
私は同意しません。私にとって、それはオブジェクト指向の観点から完全に理にかなっています。Int32にはセマンティクスがなく、純粋に「技術的な」タイプです。しかし、オブジェクトを識別する目的に役立ち、たまたまInt32として実装される抽象的な識別子型が必要です(ただし、長くても何でもかまいません)。ProductIdの具体的なインスタンスを識別するIdentifierから派生するProductIdのような具体的なスペシャライゼーションがあります。OrderItemIdの値をProductIdに割り当てることは論理的に意味がありません(明らかにエラーです)ので、型システムがこれから私たちを保護できるのは素晴らしいことです。
qbd 16

1
列挙型の使用に関して、C#がこれほど柔軟であるとは知りませんでした。列挙型は可能な値の列挙であるという事実(?)に慣れている開発者として、私を混乱させています。通常、名前付きIDの範囲を指定します。そのため、このタイプは、範囲がなく名前がないという意味で「乱用」されており、タイプのみが残されています。そして、明らかに、このタイプはそれほど安全ではありません。もう1つ:例は明らかにデータベースアプリケーションであり、ある時点で「enum」は整数型フィールドにマップされ、その時点で推定型を安全に失うことになります。
マーティンマート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.