为什么字符串类型的默认值是null而不是空字符串?


218

这是很烦人的测试我所有的字符串null之前,我可以放心地运用类似的方法ToUpper()StartWith()等...

如果的默认值为string空字符串,则无需进行测试,并且我认为它与其他值类型(例如)int或更一致double。另外Nullable<String>将是有道理的。

那么,C#的设计者为什么选择使用null字符串的默认值呢?

注意:这与这个问题有关,但更多地集中在为什么而不是怎么做上。


53
您是否认为其他参考类型有此问题?
乔恩·斯基特

17
@JonSkeet不,但这只是因为我最初错误地认为字符串是值类型。
Marcel

21
@Marcel:这是一个很好的理由。
TJ Crowder

7
@JonSkeet是的。哦,是的。(但是您对非空引用类型的讨论并不陌生……)
Konrad Rudolph 2013年

7
我相信,如果在您不希望使用字符串的地方使用断言,您会拥有更好的时间null(并且我建议您从概念上将null字符串当作空来对待)。空值可能是某处错误的结果,而空字符串应传达不同的含义。
diegoreymendez

Answers:


312

为什么字符串类型的默认值是null而不是空字符串?

因为string引用类型,所有引用类型的默认值为null

在我可以安全地应用ToUpper(),StartWith()等方法之前,测试所有字符串是否为null非常烦人...

这与引用类型的行为一致。在调用其实例成员之前,应先检查一下是否有空引用。

如果string的默认值为空字符串,则无需进行测试,并且我觉得它与其他值类型(例如int或double)更加一致。

将默认值分配给特定的引用类型null会使其不一致

另外Nullable<String>将是有道理的。

Nullable<T>适用于值类型。值得注意的是,它Nullable不是在原始.NET平台上引入的,因此如果他们更改了该规则,将会有很多损坏的代码。(@ jcolebrand提供)


10
@HenkHolterman一个人可以实现很多事情,但是为什么要引入这种明显的矛盾呢?

4
@delnan-“为什么”是这里的问题。
Henk Holterman

8
@HenkHolterman和“一致性”是对您的观点的反驳:“可以像对待其他引用类型一样对待字符串”。

6
@delnan:我正在研究一种将字符串视为值类型的语言,并且在dotnet上工作了2年以上,我同意Henk的观点。我将其视为dotnet上的主要FLAW
Fabricio Araujo

1
@delnan:可以创建一种行为基本上类似于的值类型String,除了(1)具有可用默认值的value-type-ish行为,以及(2)每次将其强制转换为可装箱的间接装箱层Object。鉴于的堆表示形式string是唯一的,因此进行特殊处理以避免多余的装箱并不是一件容易的事(实际上,能够指定非默认装箱行为对于其他类型也是一件好事)。
2013年

40

哈比卜是对的-因为 string它是引用类型。

但更重要的是,您不必null每次使用时都进行检查。但是,ArgumentNullException如果有人将您的函数传递给了null引用,您可能应该抛出a 。

事情就是这样- NullReferenceException如果您尝试调用.ToUpper()字符串,框架仍然会为您抛出a 。请记住,即使测试您的参数,这种情况仍然可能发生,null因为作为参数传递给函数的对象上的任何属性或方法都可能计算为null

话虽这么说,检查空字符串或空值是做一个平常的事,所以他们提供String.IsNullOrEmpty()String.IsNullOrWhiteSpace()只是这个目的。


30
您永远NullReferenceException不要丢下自己(msdn.microsoft.com/en-us/library/ms173163.aspx);ArgumentNullException如果您的方法不能接受空引用,则抛出。此外,在解决问题时,NullRef通常是诊断中最困难的异常之一,因此,我认为不检查null的建议不是一个很好的建议。
安迪

3
@Andy“ NullRef通常是最难诊断的异常之一”,我非常不同意,如果您记录日志,则查找和修复(仅处理null情况)确实很容易。
Louis Kottmann

6
抛出ArgumentNullException具有能够提供参数名称的其他好处。在调试过程中,这节省了...错误,几秒钟。但是重要的几秒钟。
科斯(Kos)

2
@DaveMarkle你可能要包括IsNullOrWhitespace太msdn.microsoft.com/en-us/library/...
内森·库普

1
我真的认为到处检查null是造成巨大代码膨胀的原因。这很丑陋,而且看起来很笨拙,很难保持一致。我认为(至少在类似于C#的语言中)一个好的规则是“禁止在生产代码中使用null关键字,在测试代码中疯狂使用它”。
2016年

24

您可以编写一个扩展方法(以其价值而言):

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

现在,这可以安全地工作:

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

100
但是请不要。另一个程序员想要看到的最后一件事是到处都有成千上万的代码都带有.EmptyNull(),只是因为第一个家伙“害怕”了异常。
戴夫·马克

15
@DaveMarkle:显然,这正是OP所寻找的。“在我可以安全地应用ToUpper(),StartWith()等方法之前测试所有字符串是否为null非常令人讨厌”
Tim Schmelter 2013年

19
评论是给OP的,不是给你的。尽管您的答案显然是正确的,但强烈建议程序员提出这样的基本问题,以免实际将您的解决方案投入到WIDE实践中,因为他们通常不会这样做。您在答案中没有讨论很多折衷,例如不透明,复杂性增加,重构困难,扩展方法的潜在过度使用以及是的。有时(很多时候)正确的答案不是正确的道路,这就是我发表评论的原因。
戴夫·马克

5
@Andy:不进行正确的空值检查的解决方案是正确地检查空值,而不是对问题进行创可贴。
戴夫·马克

7
如果您在编写时遇到麻烦.EmptyNull(),为什么不直接(str ?? "")在需要的地方使用它呢?也就是说,我同意@DaveMarkle的评论中表达的观点:您可能不应该这样做。null并且String.Empty在概念上是不同的,因此您不必将一个对待相同。
CVn

17

从C#6.0开始,您还可以使用以下内容

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

字符串结果将为空。


1
正确的说,从c#6.0开始,IDE的版本与它无关,因为这是语言功能。
Stijn Van Antwerpen

3
另一种选择-public string Name { get; set; } = string.Empty;
Jaja哈里斯

这个叫什么?myString?.ToUpper();
猎人尼尔森

1
它称为空条件运算符。您可以在此处阅读有关的信息msdn.microsoft.com/zh-cn/magazine/dn802602.aspx
russelrillema

14

空字符串和空值本质上是不同的。null是缺少值,而空字符串则是空值。

编程语言对变量(在这种情况下为空字符串)的“值”进行假设,将与使用不会引起空引用问题的任何其他值初始化字符串一样好。

同样,如果将字符串变量的句柄传递给应用程序的其他部分,则该代码将无法验证您是否故意传递了空白值或忘记填充该变量的值。

另一个可能出现问题的情况是字符串是某个函数的返回值。由于string是引用类型,从技术上讲,其值可以为null和empty,因此该函数从技术上也可以返回null或empty(没有阻止它这样做的方法)。现在,由于存在2个“不存在值”的概念,即一个空字符串和一个空值,因此使用此函数的所有代码都必须进行2个检查。一个代表空,另一个代表空。

简而言之,对于一个状态仅具有1个表示总是有好处的。有关空值和空值的更广泛讨论,请参见下面的链接。

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

处理用户输入时,NULL与空


2
在文本框中说,您如何确切地看到这种差异?用户是否忘记在字段中输入值,还是有意将其留为空白?编程语言中的Null确实具有特定的含义。未分配。我们知道它没有值,它与数据库null不同。
安迪

1
与文本框一起使用时,没有太大区别。无论哪种方式,具有一个表示字符串中不存在值的符号都是最重要的。如果我必须选择一个,我会选择null。
Nerrve

在Delphi中,字符串是一个值类型,因此不能为null。在这方面,它使工作变得更加轻松-我真的很讨厌将字符串作为引用类型。
Fabricio Araujo

1
在.net之前的COM(通用对象模型)下,字符串类型将包含指向字符串数据的指针,或null表示空字符串。如果他们选择这样做,.net可以采用多种方式实现相似的语义,特别是考虑到String具有多种特性的.net无论如何都要使其具有唯一性(例如,它和两个数组类型是唯一分配其类型的类型)。大小不是恒定的]。
supercat

7

根本原因/问题是,CLS规范的设计者(定义了语言与.net的交互方式)没有定义类成员可以指定必须直接调用而不是通过它们调用的方法。callvirt,而不由调用者执行空引用检查;它也没有提供定义不会受到“正常”拳击的结构的手段。

如果CLS规范定义了这样一种方法,则.net可以始终遵循公共对象模型(COM)建立的引导,在这种引导下,空字符串引用在语义上等效于空字符串,对于其他用户定义的不可变类类型,应该具有值语义来同样定义默认值。本质上,将对的每个成员发生什么事String,例如Length写成[InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }。这种方法本来可以为应该像值一样表现的事物提供非常好的语义,但是由于实现问题,需要将其存储在堆中。这种方法最大的困难在于,此类类型和Object可能会有些模糊。

一种替代方法是允许定义特殊结构类型,这些特殊结构类型不继承自Object而是具有自定义装箱和拆箱操作(这将与其他类类型相互转换)。在这种方法下,将有一个类类型NullableString,其行为现在类似于字符串,还有一个自定义框结构类型String,该类型将容纳一个Valuetype的私有字段String。如果非null或null,则尝试将转换StringNullableStringObject将返回。尝试强制转换为,实例的非null引用会将引用存储在ValueString.EmptyStringNullableStringValue(如果长度为零,则可能存储null);投射任何其他引用都将引发异常。

即使字符串必须存储在堆中,从概念上讲,也没有理由不使它们的行为不像具有非空默认值的值类型。将它们存储为包含引用的“常规”结构对于使用它们作为“字符串”类型的代码本来是有效的,但是当强制转换为“对象”时会增加一层间接性和无效性。尽管我不希望.net在今天晚些时候添加上述任何一项功能,但也许未来框架的设计人员可能会考虑将它们包括在内。


1
作为一个经常使用SQL的人,并且处理了Oracle的麻烦(不区分NULL和零长度),我很高兴.NET 做到了。“空”是一个值,“空”不是。

@JonofAllTrades:我不同意。在应用程序代码上,除了处理db代码外,没有任何意义将字符串视为类。这是一种值类型,是一种基本类型。超级猫:向您+1
Fabricio Araujo,

1
数据库代码是一个很大的“例外”。只要在某些问题域中需要区分“当前/已知,一个空字符串”和“不存在/未知/不适用”(例如数据库),那么该语言就需要支持它。当然,.NET现在已经Nullable<>可以将字符串重新实现为值类型。我不能说这种选择的成本和收益。

3
@JonofAllTrades:处理数字的代码必须具有带外方法,可以将默认值零与“未定义”区分开。实际上,与字符串和数字一起使用的可空处理代码必须对可空字符串使用一种方法,对可空数字使用另一种方法。即使可为空的类类型stringNullable<string>以前更有效,与必须对所有可为空的数据数据库值使用相同的方法相比,必须使用“更有效”的方法负担更大。
超级猫

5

因为字符串变量是引用,而不是实例

默认情况下可以将其初始化为Empty,但是会在整个板上引入许多不一致之处。


3
没有特殊的原因string必须是引用类型。可以肯定的是,组成字符串的实际字符当然必须存储在堆中,但是考虑到字符串已经在CLR中得到了专门的支持,拥有System.String一个带有Value类型的单个私有字段HeapString。该字段将是引用类型,默认为null,但是字段为null 的String结构Value将表现为空字符串。这种方法的唯一缺点是...
supercat

1
... 如果在运行时缺少特殊情况的代码,则对Stringto进行转换Object将导致String在堆上创建盒装实例,而不是简单地复制对的引用HeapString
2013年

1
@supercat-没有人说字符串应该/应该是值类型。
Henk Holterman

1
除了我没有人 将字符串设置为“特殊”值类型(具有私有引用类型字段)将使大多数处理基本上与现在一样高效,除了对方法/属性(如.Length等)添加了空检查之外,这样实例空引用不会尝试取消引用,而是表现为对空字符串适当。string如果有人希望default(string)成为一个空字符串,那么采用这种方式实施框架会好还是坏?
supercat 2013年

1
... string在引用类型字段上使用值类型包装器将是需要对.net其他部分进行最少更改的方法[事实上,如果愿意接受转换StringObject创建额外的框式项目,一个人可能只是String一个普通的结构,Char[]而它的类型字段从未公开过]。我认为拥有HeapString类型可能会更好,但是在某些方面,持有a的value-type字符串Char[]会更简单。
2013年

5

为什么C#的设计人员选择使用null作为字符串的默认值?

因为字符串是引用类型,所以引用类型的默认值为null。引用类型的变量存储对实际数据的引用。

default在这种情况下,我们使用关键字;

string str = default(string); 

strstring,因此它是引用类型,因此默认值为null

int str = (default)(int);

strint,因此它是一个值类型,因此默认值为zero


4

如果的默认值为string空字符串,则无需测试

错误!更改默认值不会改变它是引用类型的事实,并且仍然可以将引用显式设置null

另外Nullable<String>将是有道理的。

正确点。不允许null任何引用类型,而是要求Nullable<TheRefType>该功能会更有意义。

那么,C#的设计者为什么选择使用null字符串的默认值呢?

与其他参考类型的一致性。现在,为什么要完全允许null引用类型?也许,这样的感觉就像C,尽管这是在还提供了语言一个可疑的设计决定Nullable


3
可能是因为Nullable仅在.NET 2.0 Framework中引入,所以在此之前不可用吗?
jcolebrand

3
感谢Dan Burton指出,稍后有人可以将引用类型的初始化值设置为null。仔细思考,可以告诉我,我最初对这个问题的意图无济于事。
Marcel

4

也许如果??在分配字符串变量时使用运算符,可能会有所帮助。

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

2

字符串是一个不可变的对象,这意味着当给定值时,旧值不会从内存中擦除,而是保留在旧位置,而新值将放置在新位置。因此,如果默认值String aString.Empty,则会浪费String.Empty在为其指定第一个值时内存中块。

尽管看起来很小,但是在初始化默认值为的大字符串数组时,它可能会变成问题String.Empty。当然,StringBuilder如果这将成为问题,则可以始终使用可变类。


感谢您提及“首次初始化”。
Marcel 2013年

3
初始化大型数组时怎么会出现问题?正如您所说的那样,由于字符串是不可变的,因此数组的所有元素都只是指向same的指针String.Empty。我错了吗?
丹·伯顿

2
任何类型的默认值都将所有位设置为零。缺省值为的唯一方法string是空字符串是允许全零位表示空字符串。有多种方法可以实现此目的,但我认为没有涉及初始化对的引用String.Empty
2013年

其他答案也讨论了这一点。我认为人们已经得出结论,将String类视为特例并提供全零以外的其他内容作为初始化是没有意义的,即使它类似于String.Empty""
2013年

@DanV:更改string存储位置的初始化行为还需要更改所有具有type字段的结构或类的初始化行为string。这将代表.net设计的巨大变化,.net目前期望将任何类型的零初始化,而不必考虑它是什么,仅保留其总大小即可。
2013年


0

也许string关键字使您感到困惑,因为它看起来与任何其他值类型声明完全一样,但实际上是该问题的System.String解释的别名。 此外,Visual Studio中的深蓝色和小写的第一个字母可能会误导您以为是蓝色。
struct


3
object关键字不一样吗?尽管可以接受,但使用起来远少于string

2
作为int的别名System.Int32。你想说什么?:)
Thorarin

@Thorari @delnan:他们都别名,但System.Int32Struct因此具有默认值,同时System.String是一个Class具有的默认值的指针null。它们以相同的字体/颜色在视觉上呈现。如果没有知识,人们可能会认为它们的行为相同(=具有默认值)。我的答案写在en.wikipedia.org/wiki/Cognitive_psychology认知心理学思想背后:-)
Alessandro Da Rugna 2013年

我可以肯定地说,安德斯·海斯伯格(Anders Hejlsberg)在9频道的采访中说了这一点。我知道堆和栈之间的区别,但是C#的想法是临时程序员不需要这样做。
Thomas Koelle

0

可空类型直到2.0才出现。

如果在语言的开头创建了可为空的类型,则字符串将是不可为空的,并且是字符串吗?本来可以为空。但是由于向后兼容,他们无法做到这一点。

很多人都在谈论ref类型或不是ref类型,但是string是一个与众不同的类,并且已经找到解决方案使之成为可能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.