230

null在我可以安全地应用诸如等方法之前ToUpper()测试我所有的字符串是很烦人的StartWith()......

如果 的默认值string是空字符串,我就不必测试了,我会觉得它与其他值类型更一致,例如intor double。另外Nullable<String>会有意义。

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

注意:这与这个问题有关,但更关注为什么而不是如何处理它。

4

15 回答 15

324

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

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

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

这与引用类型的行为是一致的。在调用它们的实例成员之前,应该检查空引用。

如果 string 的默认值为空字符串,我就不必测试了,我会觉得它与其他值类型更一致,例如 int 或 double。

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

另外Nullable<String>会有意义。

Nullable<T>适用于值类型。值得注意的是原始.NET 平台Nullable上并未引入这一事实,因此如果他们更改了该规则,将会有很多损坏的代码。(礼貌@jcolebrand

于 2013-01-15T12:17:51.433 回答
40

Habib 是对的——因为string它是一个引用类型。

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

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

话虽如此,检查空字符串或空值是很常见的事情,因此它们提供String.IsNullOrEmpty()String.IsNullOrWhiteSpace()仅用于此目的。

于 2013-01-15T12:20:44.967 回答
24

您可以编写一个扩展方法(对于它的价值):

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

现在这可以安全地工作:

string str = null;
string upper = str.EmptyNull().ToUpper();
于 2013-01-15T12:22:38.600 回答
17

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

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

字符串结果将为空。

于 2015-10-29T06:58:42.100 回答
14

空字符串和空值是根本不同的。null 是没有值,而空字符串是空值。

对变量的“值”做出假设的编程语言,在这种情况下是一个空字符串,将与使用不会导致空引用问题的任何其他值初始化字符串一样好。

此外,如果您将该字符串变量的句柄传递给应用程序的其他部分,那么该代码将无法验证您是否有意传递了一个空白值,或者您是否忘记了填充该变量的值。

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

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

https://softwareengineering.stackexchange.com/questions/32578/sql-empty-string-vs-null-value

处理用户输入时的 NULL vs Empty

于 2013-01-15T13:27:44.850 回答
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 一样,以及一个自定义装箱的 struct type String,它将包含一个Valuetype的私有字段String。尝试将 a 转换StringNullableStringor如果非 null 或nullObject将返回。尝试强制转换为,对实例的非空引用会将引用存储在中(如果长度为零,则可能存储 null );强制转换任何其他引用都会引发异常。ValueString.EmptyStringNullableStringValue

尽管字符串必须存储在堆上,但从概念上讲,它们不应该表现得像具有非空默认值的值类型。将它们存储为包含引用的“普通”结构对于将它们用作“字符串”类型的代码来说是有效的,但在转换为“对象”时会增加一层额外的间接性和低效率。虽然我预计 .net 不会在这么晚的时候添加上述任何一个特性,但未来框架的设计者可能会考虑包括它们。

于 2013-01-15T16:55:12.637 回答
6

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

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

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

string str = default(string); 

str是 a string,所以它是一个引用类型,所以默认值为null

int str = (default)(int);

str是一个int,所以它是一个值类型,所以默认值是zero

于 2013-01-15T12:20:16.530 回答
5

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

默认情况下将其初始化为 Empty 是可能的,但它会在整个板上引入很多不一致的地方。

于 2013-01-15T12:18:17.900 回答
4

如果默认值为string空字符串,我就不必测试

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

另外Nullable<String>会有意义。

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

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

与其他引用类型的一致性。现在,为什么要允许null引用类型呢?可能是因为它感觉像 C,尽管这是一个有问题的设计决策,它也提供了Nullable.

于 2013-01-15T16:26:00.210 回答
4

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

string str = SomeMethodThatReturnsaString() ?? "";
// if SomeMethodThatReturnsaString() returns a null value, "" is assigned to str.
于 2015-11-14T19:49:51.450 回答
2

String 是一个不可变对象,这意味着当给定一个值时,旧值不会从内存中消失,而是保留在旧位置,而新值则放在新位置。因此,如果默认值String a是,那么当它被赋予第一个值时String.Empty,它会浪费内存中的块。String.Empty

尽管看起来微不足道,但在使用默认值初始化大量字符串数组时,它可能会变成一个问题String.Empty。当然,StringBuilder如果这会成为问题,您总是可以使用可变类。

于 2013-01-15T19:11:09.360 回答
2

由于 string 是引用类型,并且引用类型的默认值为 null。

于 2013-01-15T21:09:09.447 回答
1

既然你提到ToUpper()了,这个用法就是我找到这个线程的方式,我将分享这个快捷方式(字符串??“”)。ToUpper():

    private string _city;
    public string City
    {
        get
        {
            return (this._city ?? "").ToUpper();
        }
        set
        {
            this._city = value;
        }
    }

似乎比:

        if(null != this._city)
        { this._city = this._city.ToUpper(); }
于 2020-10-28T17:30:32.473 回答
0

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

于 2013-01-15T14:19:15.687 回答
0

可空类型直到 2.0 才出现。

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

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

于 2015-02-17T15:11:16.717 回答