空の文字列ではなく、文字列型のデフォルト値がnullになるのはなぜですか?


218

すべての文字列をテストするのは非常に面倒です null私のような方法で安全に適用することができる前にToUpper()StartWith()など...

のデフォルト値stringが空の文字列の場合、テストする必要はなく、次のような他の値の型との一貫性が高いと感じます。intまたはなどのdoubleます。さらにNullable<String>意味があります。

では、なぜC#の設計者nullは文字列のデフォルト値として使用することを選択したのでしょうか。

注:これはこの質問に関連していますが、それをどう処理するかではなく、その理由に重点を置いています。


53
これは他の参照タイプの問題だと思いますか?
Jon Skeet 2013年

17
@JonSkeetいいえ。ただし、私が最初に誤って、文字列は値型であると思ったからです。
マルセル

21
@マルセル:それはそれについて疑問に思うのはかなり良い理由です。
TJクラウダー2013年

7
@JonSkeetはい。そうそう。(ただし、null不可の参照型のディスカッションは見知らぬ人ではありません…)
Konrad Rudolph

7
文字列にアサーションを期待しない場所で使用すると、はるかに良い時間になるnullと思います(また、概念的にnull空の文字列を別のものとして扱うことをお勧めします)。空の文字列は別の意味を伝えるはずですが、null値はどこかのエラーの結果である可能性があります。
diegoreymendez 2013年

回答:


312

空の文字列ではなく、文字列型のデフォルト値がnullになるのはなぜですか?

のでstringある参照型とデフォルト値は、すべての参照型のためですnull

ToUpper()、StartWith()などのメソッドを安全に適用する前に、すべての文字列のnullをテストするのは非常に面倒です...

これは、参照型の動作と一致しています。インスタンスメンバーを呼び出す前に、null参照をチェックする必要があります。

文字列のデフォルト値が空の文字列である場合、テストする必要はありません。たとえば、intやdoubleなどの他の値の型との一貫性が高いと感じます。

デフォルト値を特定の参照タイプ以外に割り当てる nullことがなるだろう矛盾

さらにNullable<String>意味があります。

Nullable<T>値タイプで機能します。注目すべきは、Nullable元の.NETプラットフォームで導入されなかったという事実であり、そのルールを変更した場合、多くの破損したコードがあったはずです。(提供:@jcolebrand


10
@HenkHolterman人はたくさんのことを実装することができますが、なぜそのような明白な矛盾を導入するのですか?

4
@delnan-ここで「なぜ」が問題でした。
Henk Holterman、2013年

8
@HenkHoltermanそして、「一貫性」は、「他の参照型とは異なり、文字列を処理できる」というあなたの主張への反論です。

6
@delnan:文字列を値の型として扱う言語に取り組み、dotnetで2年以上働いているので、Henkに同意します。私はそれをdotnetの主要なFLAWと見ています。
Fabricio Araujo

1
@delnan:String(1)使用可能なデフォルト値を持つというvalue-type-ishの動作、および(2)キャストされたときはいつでもボクシングインダイレクションの不幸な追加レイヤーを除いて、本質的にのように動作する値タイプを作成できますObject。のヒープ表現stringが一意であることを考えると、余分なボクシングを回避するために特別な処理を行うことはそれほど大きな拡張ではありませんでした(実際には、デフォルト以外のボクシング動作を指定できることは、他のタイプでも同様です)。
スーパーキャット2013年

40

Habibは正しいです- stringは参照型だからです。

しかし、さらに重要なことに、使用するたびに確認する必要はありませんnull。ただしArgumentNullException、誰かが関数にnull参照を渡した場合は、おそらくをスローする必要があります。

NullReferenceExceptionこれが問題です.ToUpper()。文字列を呼び出そうとすると、フレームワークがとにかくスローします。nullパラメータとして評価される可能性のある関数に渡されたオブジェクトのプロパティまたはメソッドはであるため、引数をテストしてもこのケースが発生する可能性があることに注意してくださいnull

そうは言っても、空の文字列やnullをチェックすることはよく行われることなので、これらの目的のためだけに提供String.IsNullOrEmpty()String.IsNullOrWhiteSpace()ています。


30
NullReferenceException自分を投げてはいけません(msdn.microsoft.com/en-us/library/ms173163.aspx); あなたは投げるArgumentNullExceptionあなたのメソッドがnull参照文献を受け入れることができない場合。また、NullRefは通常、問題を修正するときに診断するのが難しい難しい例外の1つであるため、nullをチェックしないという推奨は非常に良いものではないと思います。
アンディ

3
@Andy「通常、NullRefは診断するのが最も難しい例外の1つです。」ログに記録する場合、非常に簡単に見つけて修正できます(nullケースを処理するだけ)。
Louis Kottmann 2013年

6
スローにArgumentNullExceptionは、パラメーター名を指定できるという追加の利点があります。デバッグ中、これにより節約できます...エラー、秒。しかし重要な秒。
Kos

2
あなたもIsNullOrWhitespaceを含めることができ@DaveMarkle msdn.microsoft.com/en-us/library/...
ネイサンクープ

1
どこでもnullをチェックすることは、膨大なコードの膨張の原因となると私は本当に思います。醜く、見た目がハッキリしていて、一貫性を保つのが困難です。(少なくともC#のような言語では)良いルールは「プロダクションコードでnullキーワードを禁止し、テストコードでクレイジーに使用する」ことだと思います。
サラ

24

あなたは(それが価値があるもののために)拡張メソッドを書くことができます:

public static string EmptyNull(this string str)
{
    return str ?? "";
}

これは安全に機能します:

string str = null;
string upper = str.EmptyNull().ToUpper();

100
しかし、しないでください。別のプログラマーが最後に見たいのは、最初の男が例外を「怖がらせた」からといって、どこにでも.EmptyNull()で何千行ものコードが書かれていることです。
Dave Markle 2013年

15
@DaveMarkle:しかし、明らかにそれはまさにOPが探していたものです。「ToUpper()、StartWith()などのメソッドを安全に適用する前に、すべての文字列をnullでテストするのは非常に面倒です」
Tim Schmelter 2013年

19
コメントはOPではなくあなたへのものでした。あなたの答えは明らかに正しいですが、このような基本的な質問をするプログラマーは、実際にあなたのソリューションをWIDEの実践に取り入れることに対して強く警告されるべきです。不透明度、複雑さの増大、リファクタリングの難しさ、拡張メソッドの潜在的な過剰使用、そしてはい、パフォーマンスなど、回答で議論しない多くのトレードオフがあります。時々(何度も)正しい答えは正しい道ではありません、そしてそれが私がコメントした理由です。
Dave Markle 2013年

5
@Andy:適切なnullチェックを行わないための解決策は、nullを適切にチェックすることであり、問​​題にバンドエイドをかけることではありません。
Dave Markle 2013年

7
を書くの.EmptyNull()に苦労している場合は、(str ?? "")代わりに、必要な場所で単に使用しないでください。そうは言っても、@ DaveMarkleのコメントで表明されている感情に同意します。おそらくそうすべきではありません。nullString.Emptyは概念的に異なり、必ずしも同じように扱うことはできません。
CVn 2013年

17

C#6.0以降では、次のものも使用できます。

string myString = null;
string result = myString?.ToUpper();

文字列の結果はnullになります。


1
正確には、c#6.0以降、これは言語機能であるため、IDEのバージョンはそれとは関係ありません。
Stijn Van Antwerpen、2016

3
別のオプション-public string Name { get; set; } = string.Empty;
JAJAハリス

これは何と呼ばれていますか?myString?.ToUpper();
ハンターネルソン

1
ヌル条件演算子と呼ばれます。これについては、msdn.microsoft.com
en

14

空の文字列とnullは根本的に異なります。nullは値がないことであり、空の文字列は空の値です。

変数の "値"(この場合は空の文字列)を仮定するプログラミング言語は、null参照の問題を引き起こさない他の値で文字列を初期化するのと同じくらい優れています。

また、その文字列変数へのハンドルをアプリケーションの他の部分に渡すと、そのコードは、意図的に空白値を渡したのか、その変数の値を入力するのを忘れているのかを検証する方法がありません。

これが問題となるもう1つの状況は、文字列がある関数からの戻り値である場合です。文字列は参照型であり、技術的にはnullと空の両方の値を持つことができるため、関数は技術的にnullまたは空を返すこともできます(そうすることを止めるものは何もありません)。ここで、「値がない」という2つの概念、つまり空の文字列とnullがあるため、この関数を使用するすべてのコードは2つのチェックを行う必要があります。1つは空、もう1つはnullです。

要するに、単一の状態に対して1つだけの表現を持つことは常に良いことです。空とnullに関するより広範な議論については、以下のリンクを参照してください。

/software/32578/sql-empty-string-vs-null-value

ユーザー入力を処理する場合のNULLと空


2
そして、テキストボックスで言うと、この違いはどの程度正確にわかりますか。ユーザーがフィールドに値を入力するのを忘れましたか、それとも意図的に空白のままにしていますか?プログラミング言語のnullには特定の意味があります。割り当てられていません。値がないことはわかっています。これはデータベースのnullとは異なります。
アンディ

1
テキストボックスで使用しても、それほど大きな違いはありません。どちらの方法でも、文字列に値がないことを表す1つの表記を持つことが最も重要です。1つを選択する必要がある場合は、nullを選択します。
Nerrve

Delphiでは、stringは値型であるため、nullにすることはできません。これは、この点で人生をはるかに楽にします-私は本当に文字列を参照型にするのはとても面倒です。
Fabricio Araujo 2013年

1
.netより前のバージョンのCOM(共通オブジェクトモデル)では、文字列型は文字列のデータへのポインターを保持するかnull、空の文字列を表します。.netが同様のセマンティクスを実装できる方法はいくつかありますが、実装することを選択した場合、特にStringそれを一意の型にする多数の特性がある場合(たとえば、それと2つの配列型が割り当ての唯一の型です)サイズは一定ではありません]。
スーパーキャット2013年

7

基本的な理由/問題は、CLS仕様(言語が.netと対話する方法を定義する)の設計者が、クラスメンバーがそれらを介してではなく、直接呼び出す必要があることを指定できる手段を定義しなかったことです。callvirtではなく、ことです。 null参照チェック。また、「通常の」ボクシングの対象にならない構造を定義するという意味もありませんでした。

CLS仕様でこのような手段が定義されていれば、.netは、Common Object Model(COM)によって確立されたリードに一貫して従うことができます。同様にデフォルト値を定義するために値のセマンティクスを持っていると想定されるユーザー定義の不変クラス型。基本的に、何が起こるかはString、たとえばのLengthようなものとして書かれるために、の各メンバーのためになります[InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }。このアプローチは、値のように動作する必要があるものに対して非常に優れたセマンティクスを提供しますが、実装の問題のため、ヒープに格納する必要があります。このアプローチの最大の難点は、そのようなタイプ間の変換のセマンティクスがObject少し曖昧になる可能性があることです。

別のアプローチは、継承せずObject、代わりにカスタムのボックス化およびボックス化解除操作(他のクラス型との間で変換される)を持つ特殊な構造体型の定義を許可することでした。このようなアプローチNullableStringでは、stringと同じように動作するクラス型と、type Stringの単一のプライベートフィールドValueを保持するカスタムボックス構造体が存在しStringます。変換しようとStringNullableStringたりObject返しますValuenull以外の場合、またはString.Emptynullの場合。にキャストしようとするStringと、NullableStringインスタンスへの非null参照は参照をValue格納します(長さがゼロの場合はnullを格納する可能性があります)。他の参照をキャストすると、例外がスローされます。

文字列はヒープに格納する必要がありますが、概念的には、null以外のデフォルト値を持つ値型のように動作してはならないという理由はありません。参照を保持する「通常の」構造としてそれらを格納することは、「文字列」型としてそれらを使用するコードには効率的でしたが、「オブジェクト」にキャストするときに間接層と非効率性の追加レイヤーが追加されました。現時点で.netが上記の機能のいずれかを追加することは想定していませんが、おそらく将来のフレームワークの設計者がそれらを含めることを検討するかもしれません。


1
SQLでよく働き、NULLと長さゼロを区別しないOracleの頭痛に対処した人と言えば、.NET がそうであることをとても嬉しく思います。「空」は値ですが、「null」はそうではありません。

@JonofAllTrades:同意しない。アプリケーションコードでは、dbコードを処理する場合を除いて、文字列をクラスとして扱う意味はありません。これは値型であり、基本的なものです。Supercat:+1 to you
Fabricio Araujo 2013年

1
データベースコードは大きな「例外」です。データベースなど、「存在する/既知、空の文字列」と「存在しない/不明/該当しない」を区別する必要がある問題ドメインがある場合、言語はそれをサポートする必要があります。もちろん、.NETにはがあるNullable<>ので、文字列は値の型として再実装できます。私はそのような選択のコストと利点について話すことができません。

3
@JonofAllTrades:数値を処理するコードには、デフォルト値のゼロと「未定義」を区別する帯域外の手段が必要です。そのまま、文字列と数値で機能するnull可能処理コードは、null可能文字列に対して1つのメソッドを使用し、null可能数値に対して別のメソッドを使用する必要があります。null許容クラスのタイプのstring方が効率的であるとしてもNullable<string>、「より効率的な」メソッドを使用することは、すべてのnull許容データデータベース値に同じアプローチを使用できるよりも負担が大きくなります。
スーパーキャット2013年

5

文字列変数は参照であり、インスタンスではありません。

デフォルトでそれを空に初期化することは可能でしたが、それは全面的に多くの矛盾をもたらしました。


3
string参照型でなければならない特別な理由はありません。確かに、文字列を構成する実際の文字は確かにヒープに格納する必要がありますが、文字列がすでにCLRで持っている専用のサポートの量を考えるSystem.Stringと、値の型がValueタイプの単一のプライベートフィールドHeapString。そのフィールドは参照型であり、デフォルトはになりますがnull、フィールドがnullのString構造体Valueは空の文字列として動作します。このアプローチの唯一の欠点は...
スーパーキャット2013年

1
... Stringto をキャストするとObject、ランタイムに特殊なコードがない場合String、単にへの参照をコピーするのではなく、ヒープ上にボックス化されたインスタンスが作成されますHeapString
スーパーキャット2013年

1
@supercat-文字列は値の型である必要があります/できると誰も言っていません。
ヘンクホルターマン2013年

1
私以外誰も。文字列を「特別な」値タイプ(プライベート参照タイプフィールドを持つ)にすると、ほとんどの処理は、メソッド/プロパティ.Lengthなどに追加されたnullチェックを除いて、現在と同じくらい基本的に効率的になります。 null参照は逆参照を試みませんが、代わりに空の文字列に対して適切に動作します。フレームワークがstringそのように実装された方が良いか悪いかdefault(string)、空の文字列になりたい場合...
supercat

1
... string参照型フィールドの値型ラッパーを持つことは、.netの他の部分に最小限の変更を必要とするアプローチです[実際、から変換を受け入れて追加のボックス化されたアイテムStringObject作成することをいとわない場合、決して公開されないString型のフィールドを持つ通常の構造体である可能性がありますChar[]HeapString型を持つ方がおそらく良いと思いますが、いくつかの点では、値型の文字列がを保持するChar[]方が簡単です。
スーパーキャット2013年

5

C#の設計者が文字列のデフォルト値としてnullを使用することを選択したのはなぜですか?

文字列は参照型なので、参照型はデフォルト値はnull。参照型の変数は、実際のデータへの参照を格納します。

defaultこの場合、キーワードを使用してみましょう。

string str = default(string); 

strはであるstringため、参照型であるため、デフォルト値はnullです。

int str = (default)(int);

strはであるintため、値の型であるため、デフォルト値はzeroです。


4

のデフォルト値stringが空の文字列の場合、テストする必要はありません

違う!デフォルト値を変更しても、それが参照タイプであり、誰かが明示的に参照をに設定できるという事実は変わりませんnull

さらにNullable<String>意味があります。

真のポイント。null参照タイプを許可せず、代わりNullable<TheRefType>にその機能を必要とする方が理にかなっています。

では、なぜC#の設計者nullは文字列のデフォルト値として使用することを選択したのでしょうか。

他の参照タイプとの整合性。では、なぜnull参照型をまったく許可するのですか?おそらく、これもを提供する言語での疑わしい設計決定ですが、Cのように感じられるようにするためですNullable


3
Nullableは.NET 2.0 Frameworkでのみ導入されたため、それ以前は利用できませんでしたか?
jcolebrand 2013年

3
後で誰かが参照型で初期化された値をnullに設定できることを指摘してくれたDan Burtonに感謝します。これをじっくりと考えてみると、問題の私の本来の意図は役に立たないことにつながります。
マルセル

4

おそらく??、文字列変数を割り当てるときに演算子を使用すると、役立つ場合があります。

string str = SomeMethodThatReturnsaString() ?? "";
// if SomeMethodThatReturnsaString() returns a null value, "" is assigned to str.

2

文字列は不変のオブジェクトです。つまり、値が指定された場合、古い値はメモリから消去されず、古い場所に残り、新しい値が新しい場所に配置されます。デフォルト値があればString aだったString.Empty、それは無駄だろうString.Empty、それがその最初の値を与えられたときに、メモリ内のブロックを。

非常に小さいように見えますが、デフォルト値で文字列の大きな配列を初期化するときに問題になる可能性がありString.Emptyます。もちろん、StringBuilderこれが問題になる場合はいつでも変更可能なクラスを使用できます。


「最初の初期化」について言及していただきありがとうございます。
Marcel、

3
大きな配列を初期化する場合、どのように問題になりますか?あなたが言ったように、文字列は不変なので、配列のすべての要素は単に同じへのポインタになりますString.Empty。私は間違っていますか?
Dan Burton

2
どのタイプのデフォルト値でも、すべてのビットがゼロに設定されます。のデフォルト値がstring空の文字列になる唯一の方法は、空の文字列の表現としてall-bits-zeroを許可することです。これを実現する方法はいくつかありますが、への参照を初期化する方法はないと思いますString.Empty
スーパーキャット2013年

他の回答もこの点について議論しました。Stringクラスを特別なケースとして扱い、全ビットゼロ以外のものを初期化として提供することは、String.Emptyまたはのようなものであっても意味がないと人々は結論付けたと思います""
DJV

@DanV:string格納場所の初期化動作を変更するには、型のフィールドを持つすべての構造体またはクラスの初期化動作も変更する必要がありましたstring。これは、.netの設計にかなり大きな変更をもたらすことになります。現在、これは、型について何も考えずに、型全体をゼロで初期化し、合計サイズのみを節約することを期待しています。
スーパーキャット2013年

2

stringは参照タイプであり、参照タイプのデフォルト値はnullであるためです。


0

string他の値型宣言とまったく同じように見えるため、キーワードが混乱しているかもしれませんが、実際にSystem.Stringは、この質問で説明されているように、エイリアスです。
また、Visual Studioの濃い青色と小文字の最初の文字は、それをであると誤解させる可能性がありstructます。


3
objectキーワードについても同じではありませんか?確かに、それはほど使用されていませんstring

2
As intのエイリアスですSystem.Int32。あなたのポイントは何ですか?:)
ソラリン2013年

彼らは両方の別名だが、:@Thorariは@delnan System.Int32されStructつつデフォルト値を持っSystem.StringているClassのデフォルト値を持つポインタを持ちますnull。それらは視覚的に同じフォント/色で表示されます。知識がなければ、それらは同じように動作する(=デフォルト値を持つ)と考えることができます。私の回答はen.wikipedia.org/wiki/Cognitive_psychology認知心理学のアイデアとともに書かれました:-)
Alessandro Da Rugna 2013年

私は、チャンネル9のインタビューでそれを言ったAnders Hejlsbergはかなり確かです。私はヒープとスタックの違いを知っていますが、C#の考え方は、カジュアルなプログラマは必要ないということです。
Thomas Koelle、

0

Null許容型は2.0までは登場しませんでした。

言語の最初にnull許容型が作成された場合、stringはnull不可で文字列になっていましたか?null可能だったでしょう。しかし、下位互換性のためにこれを行うことはできませんでした。

多くの人がref-typeまたはref-typeについて話しますが、stringは通常のクラスの外であり、解決策がそれを可能にすることがわかったでしょう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.