- ベストアンサー
c# nullは空集合として扱かってよいか
こんにちは、c#初心者です。 現在、ある集合を表すクラスを作っているのですが、そこで集合がある集合に含まれているかどうかを判断するメソッドで、当然nullチェックが必要なのですが、そのときにnullを空集合として扱ってよいかどうかを判断しかねて質問するに至りました(空集合として扱ってよいなら、クラス内部はいろいろと楽なんですが…)。 EventArgsのようにstaticなEmptyフィールドまたはプロパティを用意してもいいのですが、もしnullを空集合として扱えるならそうしなくても良いかなと悩んでいます。 どなたか詳しい方がいらっしゃいましたら教えていただけませんか?
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
> とりあえず、Emptyフィールドかプロパティを作ればいいということですか? その場合、フィールドかプロパティかの使い分けがわかりません(ちなみに外部から書き換え可能なメンバはない)。 public static readonlyなフィールドでよいと思います。 EmptyやNullといったメンバは知っている限りフィールドですので。 References) http://msdn.microsoft.com/ja-jp/library/system.string.empty.aspx http://msdn.microsoft.com/ja-jp/library/system.eventargs.empty.aspx http://msdn.microsoft.com/ja-jp/library/system.io.textwriter.null.aspx > また、「上位集合のフィールドだけを持っていれば十分なので」とは言いましたが、下位集合(または両方)を持っていたほうが良いのでしょうか? 比較にしか使わないのですが…。 必要であれば持てばいいですし,必要なければ持たなければよいです。 ただ一般化するならば,「全ての集合の部分集合」である空集合が存在するので,「自身の部分集合」の集合を保持するのがよいと思われます。 全ての集合 (空集合を含む) が空集合を部分集合として持っておけば,包含関係のテストにおいて空集合を特別扱いする必要がなくなります。 逆に上位集合を持つ場合,空集合は自身の上位集合を全て持つことができないため,空集合を特殊扱いする必要が出てきます。 # A.IsSupersetOf(B)はB.IsSubsetOf(A)と等価のはずなので,部分集合であるかの判定ができれば上位集合であるかの判定も可能。 > また、比較演算子「<」「>」「<=」「>=」を(⊂、⊃、、⊆、⊇に見立てて)オーバーロードしてもよいのでしょうか? > if ( set1.IsSupersetOf(set2) ) … よりは > if ( set1 >= set2 ) … のほうがすっきりするのですが、引数がnullの場合が面倒ですが、「==」「!=」以外は例外をスローしてもいいんですよね? 「包含関係を大小比較の演算子で代用する」というのが一般的であるのであればよいですが,そうでないならばやめた方がよいです。 演算子の意味が変わると,すっきりするように見えても読みにくくなる可能性が高いです。 C++のストリームの<<や>>ほど広まれば別ですが,あれも酷評されていたりもしますし, 後で紹介するMSのガイドラインでもストリーム入出力にシフト演算子を使うな,と書かれています。 なお,比較演算子を定義する場合,CompareToという代替メソッドを定義するようにしてください。 MSDN: 演算子のオーバーロード http://msdn.microsoft.com/ja-jp/library/ms229032.aspx また,IComperer.CompareやIComparable.CompareToの制約から,比較演算子でnullを渡された場合は「最小」として扱うべきで,null同士の比較は「等しい」とする必要があります。 さらに,「型が異なる」ことを理由としたArgumentException以外の例外も発生させるべきではありません。 MSDN: IComparer.Compare メソッド (System.Collections) http://msdn.microsoft.com/ja-jp/library/system.collections.icomparer.compare.aspx > 一つ気になるのが、たとえば、「SampleSet animal」と「SamoleSet dog」があれば、「animal > dog」はtrueで「animal < dog」はfalseになりますが、同じく「SampleSet cat」の場合 > 「dog < cat」はfalse、「dog > cat」もfalseになるのですが、「1 < 2」などとは判断の仕方が異なるのでOKなのでしょうか? OKの場合、「null < animal」はfalseでもよいのでしょうか? 比較不可能なものがあるならば,そもそも比較演算子を定義すべきではないでしょう。 比較演算子は,プリミティブ型からの類推で全順序集合にすべきです。 なお,Double.Nanは「最小値」として,default(string)も「最小値」として取り扱われるため,これらは全順序集合になっています。 # stringには比較演算子はないですが,CompareToがあるため例に入れています。 > 最後に、set1.IsSupersetOf(set2)とset1.IsSuperset(set2)のどちらが良いのでしょうか? (文字列長をとるか、文法をとるか) 個人的には,Ofなどを付ける方です。 set1.IsSuperset(set2)では, ・set1がset2の上位集合であるかどうかを判別する ・set2がset1の上位集合であるかどうかを判別する のどちらの流儀も存在し得ます。 しかし,Ofを付けることで前者の解釈であることがわかりやすくなります。 ちなみに,System.Type型のIs系のメソッドの一部 (Typeのみを引数にとる物) は,From/To/Ofを付けて明確にしています。 MSDN: Type メソッド (System) http://msdn.microsoft.com/ja-jp/library/system.type_methods IDEによる自動補完が効く昨今,Ofなどの数文字程度であれば問題になりません。 2文字けちって将来色々調べたりバグに泣くよりも,2文字増やしてでも紛れをなくす方が有用です。
その他の回答 (4)
- Yune-Kichi
- ベストアンサー率74% (465/626)
「空集合かもしれない集合」が「別の集合」に含まれているかを判断することはないのでしょうか。 var set1 = GetSet(condition1); // なんらかの条件から集合を取得する。条件次第では空集合かもしれない var set2 = GetSet(condition2); // 別の条件から集合を取得する。条件次第では空集合かもしれない if (set1.IsSubsetOf(set2)) { // set1はset2の部分集合 } こういう処理を行う場合,空集合がインスタンスでないと, if (set1 == null || set1.IsSubsetOf(set2)) { // set1はset2の部分集合。空集合は任意の集合の部分集合 } と,使う側に空集合の場合の特殊な判断が必要になります。 また,包含関係ではなく帰属関係においても,やはり if (set2.HasElement(set1)) { // set1はset2の要素 } という処理も, if (set2 != null && set2.HasElement(set1)) { // set1はset2の要素 } と,使う側で空集合を排除するコードが必要になります。 空集合は集合なので,集合演算のような(空集合を含む)集合全体で存在する演算に対して, 空集合に対する場合のみ特殊な処理をクラスを使う側に要求すべきではないでしょう。 その観点から,インスタンスのpublicメンバが存在しない場合を除くと, 空集合にnullを割り当てるのはよくない設計といえます。
お礼
回答ありがとうございます。う~ん、なかなか難しいですね。 とりあえず、Emptyフィールドかプロパティを作ればいいということですか? その場合、フィールドかプロパティかの使い分けがわかりません(ちなみに外部から書き換え可能なメンバはない)。 また、「上位集合のフィールドだけを持っていれば十分なので」とは言いましたが、下位集合(または両方)を持っていたほうが良いのでしょうか? 比較にしか使わないのですが…。 (次々に(思いついたまま)質問します。ご了承ください) また、比較演算子「<」「>」「<=」「>=」を(⊂、⊃、、⊆、⊇に見立てて)オーバーロードしてもよいのでしょうか? if ( set1.IsSupersetOf(set2) ) … よりは if ( set1 >= set2 ) … のほうがすっきりするのですが、引数がnullの場合が面倒ですが、「==」「!=」以外は例外をスローしてもいいんですよね? 一つ気になるのが、たとえば、「SampleSet animal」と「SamoleSet dog」があれば、「animal > dog」はtrueで「animal < dog」はfalseになりますが、同じく「SampleSet cat」の場合 「dog < cat」はfalse、「dog > cat」もfalseになるのですが、「1 < 2」などとは判断の仕方が異なるのでOKなのでしょうか? OKの場合、「null < animal」はfalseでもよいのでしょうか? 最後に、set1.IsSupersetOf(set2)とset1.IsSuperset(set2)のどちらが良いのでしょうか? (文字列長をとるか、文法をとるか)
- maru_yoshi_
- ベストアンサー率39% (17/43)
空集合は要素がない集合のこと。 「集合を表すクラス」を考えているなら null は「空」の集合ではなく、集合そのものが存在しない状態。 集合が存在しないことと要素が存在しない集合は異なる概念なので、避ける方が賢明。 中身が空の入れ物がある状態と、そもそも入れ物がない状態は分けておいた方がいいと思う。 空集合と集合がない場合と同一視してもよいとあなたが判断したのであれば、間違いとは言えない。 でも、集合に要素を追加することを考えると、そのメソッドは集合クラスのメソッドになるだろうから、空集合が null だとするとメソッドの呼び出しができないような気が...
お礼
回答ありがとうございます。 > 集合が存在しないことと要素が存在しない集合は異なる概念なので、 > 避ける方が賢明。 やっぱりそうですよね。ただ、うまく言葉にできませんでした。 > 中身が空の入れ物がある状態と、 > そもそも入れ物がない状態は分けておいた方がいいと思う。 確かにそうなのですが、No.2さんのお礼に書いているとおり、コレクションでもなく、中身のない、関係を表すだけの実体があるんだかないんだか分からないクラスなんです(すみません、説明不足で)。それを踏まえたうえで時間があるようでしたらもう一度ご意見を伺わせてください。
- Yune-Kichi
- ベストアンサー率74% (465/626)
nullはオブジェクトのインスタンスではありません。 つまり,インスタンスメソッドやプロパティへのアクセスは許されません。 このため,集合クラスにおいてnullを空集合のかわりとして「常に」使うことはよくない設計となります。 例えば,部分集合を返すメソッドが空集合をnullで返すとなると, それ以降空集合のインスタンスに対して問題が無い場合であっても, 空集合のみ特別扱いしないといけなくなるわけです。 さて,上記は原則ではあるものの,引数としてnullを受け付けた場合,いくつかの方法があります。 ・nullを無視して処理する ・何らかの「初期値」として処理する ・ArgumentNullExceptionを発生させる 今回の場合,つまり部分集合かどうかを調べるのであれば, ・引数としてのnullは空集合とみなす ・nullは集合ではないのだからArgumentNullExceptionを発生させる といった対応をとることができます。これは,メソッドの約束事の範疇になります。 他のメソッドにおいても常にnullを空集合と見なせるのであれば前者でよいでしょう。 ただ,他のメソッドにおいてnullを空集合と見なすと都合が悪いことがあるのであれば,後者にしておくと一貫性が出て使いやすくなると思います。
お礼
解答ありがとうございます。 > nullはオブジェクトのインスタンスではありません。 > つまり,インスタンスメソッドやプロパティへのアクセスは許されません。 > このため,集合クラスにおいてnullを空集合のかわりとして「常に」 > 使うことはよくない設計となります。 う~ん。そんな感じはしていたのですが、うまく言葉にできなかっかんですよね。 ただ、(説明不足で誤解を招いたのかもしれませんが)今回のクラスはコレクションじゃないんです。単に漠然と「集合」というものを表して、「この集合Aは集合Bに属している」、「集合Cは集合Bと集合Dに属している」のような関係だけを表す実体があるんだかないんだか分からないクラスなんです。だから「null = empty set」でもよいかと。 空集合を作るのが面倒な理由はこのためです。細かく言うと、ある集合Aにある集合Bが含まれているかどうかを判断するだけの場合、上位集合のフィールドだけを持っていれば十分なので、上位集合フィールドだけでは空集合を表現できないんです。 (下位集合フィールドにした場合、上位集合を保持するよりもはるかに大きいサイズの配列が必要(普通、上位集合より下位集合のほうが多い→含まれているかどうか判断するためのメソッドの効率も低下する)うえに、下位集合は増える可能性が十分にあるためListを使うか下位集合数を保持するフィールドが必要なため、いろいろと不便。また、下位集合を持っていなくても空集合である保障はないため結局無駄!…というのが初心者からの視点だが、自信はあまりない) すると、Emptyフィールド(ないしはプロパティ)を特別扱いしなければならず、面倒。具体的には if ( set1 == Empty ) return -1; if ( set2 == Empty ) return 1; が増えることになります。一貫性があり保守性の高いものにしたい、が、無駄なコードは増やしたくない…。いろいろ考えていると余計に分からなくなってきたので、暇があればもう少しアドバイスをお願いします(でも、結局は例外判定かな?)。
- wormhole
- ベストアンサー率28% (1626/5665)
.NET Framework 4のSystem.Collections.Generic.SortedSet<T>やSystem.Collections.Generic.HashSet<T>に合わせるとよいのではないでしょうか。
お礼
解答ありがとうございます。 参考にはなりましたが、なにせ、現在作っている集合が、中身のない「~に属している」ことだけを表す集合なので、これには使えそうにないです。
お礼
回答ありがとうございます。 お蔭様でスッキリしました。本当にありがとうございます。 これからも何度もお世話になると思いますが、そのときはよろしくお願いします。