【问题标题】:Why is the default value of the string type null instead of an empty string?为什么字符串类型的默认值是 null 而不是空字符串?
【发布时间】:2012-12-29 12:58:38
【问题描述】:

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

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

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

注意:这与this question 相关,但更侧重于为什么而不是如何处理它。

【问题讨论】:

  • 您认为这是 other 引用类型的问题吗?
  • @JonSkeet 不,只是因为我最初错误地认为字符串是值类型。
  • @Marcel:这是思考这个问题的一个很好的理由。
  • @JonSkeet 是的。哦是的。 (但您对不可空引用类型的讨论并不陌生……)
  • @JohnCastle 我敢问你了解三元状态价值的数据库开发人员是否可以从他们那里获取空值。它不好的原因是因为人们不认为是三位一体的,它是左或右,上或下,是或否。关系代数需要三元状态。

标签: c# string default-value


【解决方案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(); }

【讨论】:

    【解决方案2】:

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

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

    字符串结果将为空。

    【讨论】:

    • 正确地说,从c# 6.0开始,IDE的版本与它无关,因为这是一个语言特性。
    • 另一个选项 - public string Name { get; set; } = string.Empty;
    • 这叫什么? myString?.ToUpper();
    • 它被称为空条件运算符。你可以在这里阅读它msdn.microsoft.com/en-us/magazine/dn802602.aspx
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      可空类型直到 2.0 才出现。

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

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

      【讨论】:

        【解决方案5】:

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

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

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

        话虽如此,检查空字符串或空值是很常见的事情,因此他们提供String.IsNullOrEmpty()String.IsNullOrWhiteSpace() 就是为了这个目的。

        【讨论】:

        • 你不应该自己扔NullReferenceException (msdn.microsoft.com/en-us/library/ms173163.aspx);如果你的方法不能接受空引用,你会抛出一个ArgumentNullException。此外,当您修复问题时,NullRef 通常是更难诊断的异常之一,因此我认为不检查 null 的建议不是一个很好的建议。
        • @Andy “NullRef 通常是最难诊断的异常之一”我强烈反对,如果你记录一些东西,它真的很容易找到和修复(只需处理 null 情况)。
        • 投掷ArgumentNullException 的额外好处是能够提供参数名称。在调试期间,这可以节省......错误,秒。但重要的几秒钟。
        • @DaveMarkle 你可能也想包含 IsNullOrWhitespace msdn.microsoft.com/en-us/library/…
        • 我真的认为到处检查 null 是导致代码膨胀的根源。它很丑,而且看起来很老套,而且很难保持一致。我认为(至少在类似 C# 的语言中)一个好的规则是“在生产代码中禁止 null 关键字,在测试代码中疯狂使用它”。
        【解决方案6】:

        你可以写一个extension method(为了它的价值):

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

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

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

        【讨论】:

        • 但请不要这样做。 另一个程序员最不想看到的就是成千上万行代码中到处都是 .EmptyNull() 只是因为第一个人“害怕” ”的例外。
        • @DaveMarkle:但显然这正是 OP 所寻找的。 “在我可以安全地应用 ToUpper()、StartWith() 等方法之前测试所有字符串是否为 null 非常烦人”
        • 评论是给 OP 的,不是给你的。尽管您的答案显然是正确的,但应强烈警告提出此类基本问题的程序员不要将您的解决方案实际投入 WIDE 实践,这通常是他们的习惯。您在答案中没有讨论许多权衡,例如不透明性、复杂性增加、重构难度、扩展方法的潜在过度使用,以及是的,性能。有时(很多时候)正确的答案并不是正确的道路,这就是我发表评论的原因。
        • @Andy:没有进行正确的空值检查的解决方案是正确地检查空值,而不是给问题贴上创可贴。
        • 如果您在编写.EmptyNull() 时遇到麻烦,为什么不在需要的地方直接使用(str ?? "") 呢?也就是说,我同意@DaveMarkle 评论中表达的观点:你可能不应该这样做。 nullString.Empty 在概念上是不同的,你不一定能把它们当作另一个。
        【解决方案7】:

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

        因为string 是一个引用类型,并且所有引用类型的默认值都是null

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

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

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

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

        另外Nullable<String> 会有意义。

        Nullable<T> 适用于值类型。值得注意的是,Nullable 并未在原始 .NET platform 中引入,因此如果他们更改了该规则,将会有很多损坏的代码。(Courtesy @jcolebrand

        【讨论】:

        • @HenkHolterman 一个人可以实现一大堆东西,但为什么要引入如此明显的不一致呢?
        • @delnan - 这里的问题是“为什么”。
        • @HenkHolterman 而“一致性”是对您的观点的反驳“字符串可以被视为不同于其他引用类型”。
        • @delnan:我正在研究一种将字符串视为值类型的语言并在 dotnet 上工作了 2 年以上,我同意 Henk 的观点。我认为这是 dotnet 上的一个主要 FLAW
        • @delnan:可以创建一种行为本质上类似于 String 的值类型,除了 (1) 具有可用默认值的值类型行为,以及 (2) 不幸任何时候将其强制转换为Object 时都会增加额外的拳击间接层。鉴于string 的堆表示是独一无二的,因此进行特殊处理以避免额外的装箱不会有太大的难度(实际上,能够指定非默认装箱行为对于其他类型也是一件好事)。
        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

          • 感谢您提到“第一次初始化”的事情。
          • 初始化一个大数组怎么会出问题呢?正如您所说,因为字符串是不可变的,所以数组的所有元素都只是指向同一个String.Empty 的指针。我弄错了吗?
          • any 类型的默认值将所有位设置为零。 string 的默认值为空字符串的唯一方法是允许所有位为零作为空字符串的表示。有很多方法可以实现这一点,但我认为不涉及初始化对 String.Empty 的引用。
          • 其他答案也讨论了这一点。我认为人们已经得出结论,将 String 类视为特殊情况并提供除全位零以外的其他内容作为初始化是没有意义的,即使它类似于 String.Empty""
          • @DanV:更改string 存储位置的初始化行为还需要更改所有具有string 类型字段的结构或类的初始化行为。这将代表 .net 设计的一个相当大的变化,它目前期望对任何类型进行零初始化,甚至不必考虑它是什么,只保留它的总大小。
          【解决方案10】:

          根本原因/问题是 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,它将保存一个Value 类型的String 类型的私有字段。尝试将String 转换为NullableStringObject 将返回Value(如果非空)或String.Empty(如果空)。尝试转换为String,对NullableString 实例的非空引用会将引用存储在Value 中(如果长度为零,则可能存储空);强制转换任何其他引用都会引发异常。

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

          【讨论】:

          • 作为一个经常在 SQL 中工作的人,并且已经解决了 Oracle 不区分 NULL 和零长度的问题,我很高兴 .NET 确实我>。 “空”是一个值,“空”不是。
          • @JonofAllTrades:我不同意。在应用程序代码上,除了处理 db 代码之外,没有任何意义将字符串视为一个类。它是一种值类型,也是一种基本类型。超级猫:+1 给你
          • 数据库代码是一个很大的“例外”。只要有一些个问题域需要区分“存在/已知,空字符串”和“不存在/未知/不适用”,比如数据库,那么语言就需要支持它。当然,既然 .NET 有 Nullable<>,字符串可以重新实现为值类型;我无法谈论这种选择的成本和收益。
          • @JonofAllTrades:处理数字的代码必须有一种带外方式来区分默认值零和“未定义”。事实上,处理字符串和数字的可空处理代码必须对可空字符串使用一种方法,而对可空数字使用另一种方法。即使可以为空的类类型stringNullable<string> 更有效,但必须使用“更有效”的方法比对所有可以为空的数据数据库值使用相同的方法更加繁琐。
          【解决方案11】:

          如果string 的默认值是空字符串,我就不用测试了

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

          另外Nullable<String> 会有意义。

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

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

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

          【讨论】:

          • 可能是因为Nullable是在.NET 2.0 Framework中才引入的,所以在此之前不可用?
          • 感谢 Dan Burton 指出稍后有人可以将引用类型的初始化值设置为 null。仔细思考后告诉我,我在问题中的初衷是没有用的。
          【解决方案12】:

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

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

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

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

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

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

          NULL vs Empty when dealing with user input

          【讨论】:

          • 您如何看到这种差异,比如在文本框中?用户是否忘记在字段中输入值,或者他们故意将其留空?编程语言中的 Null 确实具有特定含义;未分配。我们知道它没有值,这与数据库 null 不同。
          • 与文本框一起使用时没有太大区别。无论哪种方式,使用一种表示法来表示字符串中没有值是最重要的。如果我必须选择一个,我会选择 null。
          • 在 Delphi 中,字符串是一种值类型,因此不能为空。在这方面它让生活变得更轻松 - 我真的觉得让字符串成为引用类型非常烦人。
          • 在早于 .net 的 COM(通用对象模型)下,字符串类型将保存指向字符串数据的指针,或 null 表示空字符串。如果他们选择这样做,.net 可以通过多种方式实现类似的语义,特别是考虑到 String 具有许多使其成为独特类型的特征[例如它和这两种数组类型是唯一分配大小不恒定的类型。
          【解决方案13】:

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

          【讨论】:

          • object 关键字不也是这样吗?虽然不可否认,这比 string 使用得少得多。
          • 因为intSystem.Int32 的别名。你想说什么? :)
          • @Thorari @delnan :它们都是别名,但 System.Int32Struct 因此具有默认值,而 System.StringClass 具有默认值为 @ 的指针987654334@。它们以相同的字体/颜色在视觉上呈现。在没有知识的情况下,人们可能会认为它们的行为方式相同(=具有默认值)。我的答案是用en.wikipedia.org/wiki/Cognitive_psychology 认知心理学的想法写的:-)
          • 我相当肯定 Anders Hejlsberg 在第 9 频道的采访中说过。我知道堆和堆栈之间的区别,但 C# 的想法是普通程序员不需要。
          【解决方案14】:

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

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

          让我们使用 default 关键字来处理这种情况;

          string str = default(string); 
          

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

          int str = (default)(int);
          

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

          【讨论】:

            【解决方案15】:

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

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

            【讨论】:

            • string 没有特别的理由必须是引用类型。可以肯定的是,组成字符串的实际字符肯定必须存储在堆上,但考虑到字符串在 CLR 中已经有大量的专用支持,让 System.String 成为一个值并不是一件容易的事使用 HeapString 类型的单个私有字段 Value 键入。该字段将是一个引用类型,并且默认为null,但String 结构,其Value 字段为null 将表现为一个空字符串。这种方法的唯一缺点是......
            • ...将String 转换为Object 会在运行时中没有特殊情况代码的情况下导致在堆上创建一个装箱的String 实例,而不是而不是简单地复制对HeapString 的引用。
            • @supercat - 没有人说字符串应该/可以是值类型。
            • 除了我没有人。将字符串作为“特殊”值类型(具有私有引用类型字段)将允许大多数处理基本上与现在一样高效,除了对 .Length 等方法/属性添加空检查,以便持有空引用的实例不会尝试取消引用它,而是表现得适合空字符串。如果希望 default(string) 是一个空字符串,那么使用这种方式实现的 string 框架是否会更好或更差......
            • ...让string 成为引用类型字段上的值类型包装器将是需要对.net 的其他部分进行最少更改的方法[确实,如果愿意的话接受从StringObject 的转换创建一个额外的盒装项目,可以简单地让String 成为一个普通的结构,其类型为Char[],它从未暴露]。我认为拥有HeapString 类型可能会更好,但在某些方面,拥有Char[] 的值类型字符串会更简单。
            猜你喜欢
            • 2015-01-31
            • 2011-03-29
            • 2014-06-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-04-26
            • 2013-10-31
            • 2013-01-19
            相关资源
            最近更新 更多