【问题标题】:How useful is C#'s ?? operator?C# 有多大用处??操作员?
【发布时间】:2010-12-13 22:56:51
【问题描述】:

所以我对 ??运营商,但仍然无法使用它。当我做类似的事情时,我通常会考虑它:

var x = (someObject as someType).someMember;

如果 someObject 有效且 someMember 为空,我可以这样做

var x = (someObject as someType).someMember ?? defaultValue;

但是当 someObject 为空时,我几乎总是会遇到问题,并且??并没有比我自己做空检查更干净。

你们找到了什么用途?在实际情况下?

【问题讨论】:

  • 仅供参考:??称为空合并运算符。
  • 是的,正是我的想法。
  • 我认为这应该是一个“社区维基”。
  • 我同意,这通常很诱人,但后来我意识到我需要使用其他东西。我几乎从来没有使用过它。

标签: c# syntactic-sugar


【解决方案1】:

??实际上是一个空检查运算符。

您的代码遇到问题的原因是,如果“someObject 为 null,则 .someMember 是 null 对象的属性。您必须先检查 someObject 是否为 null。

编辑:

我应该补充一点,是的,我确实找到了??非常有用,尤其是在处理数据库输入时,尤其是来自 LINQ 的输入,但在其他情况下也是如此。我经常使用它。

【讨论】:

    【解决方案2】:

    我通常将它用于字符串或可为空的类型。

    string s = someString ?? "Default message";
    

    容易
       string s;
       if(someString == null)
         s = "Default Message";         
       else
         s = someString;
    

      string s = someString != null ? someString : "Default Message";
    

    【讨论】:

    • 非常正确,但它不等同于你的中间语句,好像 someString == string.Empty,??运算符不会触发。
    • @跛脚鸭,你是对的。空字符串与空字符串不同,这只是一个示例。真的,我可以用空检查替换字符串检查。我应该怎么做...
    • 另见 string.IsNullOrEmpty(s)。
    • @TrueWill,这就是我在编辑之前所拥有的,但正如 The Lame Duck 指出的那样,String.IsNUllOrEmpty 与空合并运算符不同。还有@Ed Swangren,谢谢。当我进行编辑时,我混淆了案例。今天是星期五,所以我只是一团糟:P
    【解决方案3】:

    ?? 运算符类似于 SQL 中的 coalesce 方法,它为您获取第一个非空值。

    var result = value1 ?? value2 ?? value3 ?? value4 ?? defaultValue;
    

    【讨论】:

    • 不错,没想到这么用!
    • 我想不出使用的情况??以这种方式,更好的设计不会消除。一个好的用例会很有趣。
    • @jphofmann:我目前工作的一个很好的用例。我必须通过调用函数来搜索复杂的业务对象。如果搜索失败,则搜索函数返回 null。我必须用不同的搜索参数反复搜索,直到找到一个对象。从这样的代码中可以很容易地理解搜索规则:var result = Find("A") ?? Find("Y") ?? Find("Q"); 使用?? 可以使您的代码更加紧凑和可读。
    【解决方案4】:

    在您的情况下,您可以将 DefaultObject 创建为静态属性并使用如下...

    var x = (someObject as someType ?? someType.DefaultObject).someMember;
    

    您的 DefaultObject 将返回一个静态的只读对象。

    如 EventArgs.Empty、String.Empty 等...

    【讨论】:

    • 这对我来说似乎很危险。如果我想要一个对象的属性,只要它不为空,那么这段代码将导致我什至不知道该对象是否存在的情况;也许它只是碰巧与DefaultObject 共享一个属性,或者它可能是空的。
    • @Dan,如果您关心对象是否存在,请检查 someObject == null。如果您只想向用户显示可读的内容,请使用 SomeType.DefaultObject。仅供参考,这种重构称为 Introduce Null Object。
    【解决方案5】:

    我遇到了与您完全相同的问题,但在使用 LinqToXML 的空间中,文档中可能没有标签,因此使用 ?? 无法合并该标签上的属性。

    因为在 C# 中没有全局变量之类的东西,所以一切都在一个类中,所以我发现和你一样,那是什么?基本没用。

    即使是一个简单的属性获取器,例如:

    MyObject MyObj() { get { return _myObj ?? new MyObj(); } }
    

    初始化器可能会更好...

    我想像在 SQL 中一样,它在 Linq 查询中会有些用处,但我现在无法真正推导出一个。

    【讨论】:

    • 默认(MyObj)?那是什么意思?无论如何它都会为空,对吧?
    【解决方案6】:

    我同意你的观点??运算符的用途通常有限——如果某些内容为空,则提供一个后备值很有用,但在您想继续深入了解有时的属性或方法的情况下,它对于防止空引用异常没有用处——空引用。

    恕我直言,除了 ?? 还需要什么?是一个“空引用”运算符,它允许您将长属性和/或方法链链接在一起,例如a.b().c.d().e 无需测试每个中间步骤是否为空。 Groovy 语言有一个Safe Navigation operator,非常方便。

    幸运的是,C# 团队似乎意识到了这个功能差距。请参阅此 connect.microsoft.com suggestion 以了解 C# 团队向 C# 语言添加 null 取消引用运算符。

    我们收到了相当多的请求 与此类似的功能。这 ”?。” 社区提到的版本 讨论最贴近我们的心—— 它允许您在 每个“点”,并与 现有的??运营商:

    a?.b?.c ?? d

    如果 a , a.b 或 a.b.c 中的任何一个是的意思 null 使用 d 代替。

    我们正在为未来考虑这个 发布,但不会出现在 C# 4.0 中。

    再次感谢,

    Mads Torgersen,C# 语言 PM

    如果您也想在 C# 中使用此功能,请将您的投票添加到连接站点上的 suggestion! :-)

    我用来解决缺少此功能的一种解决方法是一种扩展方法,如this blog post 中描述的那样,允许这样的代码:

    string s = h.MetaData
                .NullSafe(meta => meta.GetExtendedName())
                .NullSafe(s => s.Trim().ToLower())
    

    如果 h、h.MetaData 或 h.MetaData.GetExtendedName() 为 null,则此代码返回 h.MetaData.GetExtendedName().Trim().ToLower() 或返回 null。我还扩展了它以检查 null 或空字符串,或者 null 或空集合。这是我用来定义这些扩展方法的代码:

    public static class NullSafeExtensions
    {
        /// <summary>
        /// Tests for null objects without re-computing values or assigning temporary variables.  Similar to 
        /// Groovy's "safe-dereference" operator .? which returns null if the object is null, and de-references
        /// if the object is not null.
        /// </summary>
        /// <typeparam name="TResult">resulting type of the expression</typeparam>
        /// <typeparam name="TCheck">type of object to check for null</typeparam>
        /// <param name="check">value to check for null</param>
        /// <param name="valueIfNotNull">delegate to compute if check is not null</param>
        /// <returns>null if check is null, the delegate's results otherwise</returns>
        public static TResult NullSafe<TCheck, TResult>(this TCheck check, Func<TCheck, TResult> valueIfNotNull)
            where TResult : class
            where TCheck : class
        {
            return check == null ? null : valueIfNotNull(check);
        }
    
        /// <summary>
        /// Tests for null/empty strings without re-computing values or assigning temporary variables
        /// </summary>
        /// <typeparam name="TResult">resulting type of the expression</typeparam>
        /// <param name="check">value to check for null</param>
        /// <param name="valueIfNotNullOrEmpty">delegate to compute non-null value</param>
        /// <returns>null if check is null, the delegate's results otherwise</returns>
        public static TResult CheckNullOrEmpty<TResult>(this string check, Func<string, TResult> valueIfNotNullOrEmpty)
            where TResult : class
        {
            return string.IsNullOrEmpty(check) ? null : valueIfNotNullOrEmpty(check);
        }
        /// <summary>
        /// Tests for null/empty collections without re-computing values or assigning temporary variables
        /// </summary>
        /// <typeparam name="TResult">resulting type of the expression</typeparam>
        /// <typeparam name="TCheck">type of collection or array to check</typeparam>
        /// <param name="check">value to check for null</param>
        /// <param name="valueIfNotNullOrEmpty">delegate to compute non-null value</param>
        /// <returns>null if check is null, the delegate's results otherwise</returns>
        public static TResult CheckNullOrEmpty<TCheck, TResult>(this TCheck check, Func<TCheck, TResult> valueIfNotNullOrEmpty)
            where TCheck : ICollection
            where TResult : class
        {
            return (check == null || check.Count == 0) ? null : valueIfNotNullOrEmpty(check);
        }
    }
    

    【讨论】:

    • 我喜欢辅助方法的想法,但很遗憾它必须是这样的黑客。
    • 是的。如果你想把它放到 C# 中,有一个简单的方法可以加快这个过程:去connect.microsoft.com/VisualStudio/feedback/… 为这个建议投票吧! :-)
    • 404 在“安全空解引用运算符”链接上。并且还在 microsoft connect 链接上。
    • @JonathonReinhart - 感谢您报告损坏的链接,我刚刚用更新的 URL 修复了它们。此外,要投票支持将此功能添加到 C#,请不要使用我上面评论中的链接,因为它也已损坏。相反,去这里:connect.microsoft.com/VisualStudio/feedback/details/432830
    • 看来我们终于可以在 c# 6 中获得这个功能了。耶。
    【解决方案7】:

    最有用的是在处理可空类型时。

    bool? whatabool;
    //...
    bool realBool = whatabool ?? false;
    

    没有 ??您需要编写以下内容,这更加令人困惑。

    bool realbool = false;
    if (whatabool.HasValue)
        realbool = whatabool.Value;
    

    我发现这个运算符对于可空类型非常有用,但除此之外,我发现自己并没有经常使用它。绝对是一个很酷的捷径。

    【讨论】:

    • 一种没有 ??是: bool realBool = (whatabool == null) ?假:whatabool;
    【解决方案8】:

    我用它来处理在正常操作下可以返回 null 的方法。

    例如,假设我有一个容器类,它有一个 Get 方法,如果容器不包含键(或者 null 是一个有效的关联),则该方法返回 null。然后我可能会做这样的事情:

    string GetStringRep(object key) {
        object o = container.Get(key) ?? "null";
        return o.ToString();
    }
    

    显然,这两个序列是等价的:

    foo = bar != null ? bar : baz;
    foo = bar ?? baz;
    

    第二个更简洁。

    【讨论】:

    • 实际上,这不是 100% 正确的。三元运算符与空合并运算符生成不同的 IL 序列。第一个看起来像这样: ldloc.0 brfalse.s isnull1 ldloc.0 br.s notnull1 isnull1: ldstr "Default" notnull1: stloc.1 后者看起来像这样: ldloc.0 dup brtrue.s notnull2 pop ldstr " Default" notnull2: stloc.1 基于此,我想说 null 合并运算符针对其目的进行了优化,而不仅仅是纯语法糖。
    • 我确实为格式混乱道歉。啊。我会将其发布为看起来更清洁的答案。
    【解决方案9】:

    用更紧凑的形式代替冗长的语法似乎是个好主意。但通常它最终只是混淆了代码而没有真正获得任何东西。现代标准提倡更长、更具描述性的变量名称,并使用更详细的布局样式,以使代码更具可读性和可维护性。

    如果我要训练我的大脑快速解析 ??,我需要找到一个它显示出明显优势的情况 - 无法以其他方式完成的事情,或者可以节省大量时间的地方打字。

    这是哪里??为我坠落。它很少有用,而且 ?: 和 if/else 是如此快速和可读的替代方案,以至于使用没有什么新东西的不同语法似乎没有什么意义。

    a = b ?? C ?? ?? e;听起来很棒。但我从来不需要这样做!

    令我恼火的是我想使用??,但每次我想“哦,也许我可以在这里使用??”时,我意识到我不想使用对象本身,而是它的成员。

    name = (user == null) ? "doe" : user.Surname;

    不幸的是,??在这里没有帮助。

    【讨论】:

    • P.S.不过,我不开发网页或 LINQ 数据库查询,所以也许??只是针对不同风格的编程......
    【解决方案10】:

    我经常将它用于属性中延迟实例化的对象,例如

    public MyObject MyProperty
    {
        get
        {
            return this.myField ?? (this.myField = new MyObject());
        }
    }
    

    这利用了在 C# 中赋值是一个表达式这一事实,因此您可以在同一语句中分配和使用表达式的结果(分配的值)。我仍然觉得这个 Code Pattern 有点脏,但它比替代方案(下)更简洁,所以它几乎赢了。

    public MyObject MyProperty
    {
        get
        {
            if (this.myField == null)
            {
                this.myField = new MyObject();
            }
    
            return this.myField;
        }
    }
    

    【讨论】:

    • 不错。如果您还可以使其线程安全,同时仍将其保持在一行上,则可以加分:-)
    【解决方案11】:

    作为一个仅供参考,三元运算符从空合并运算符生成不同的 IL 序列。第一个看起来像这样:

    // string s = null;
    // string y = s != null ? s : "Default";
        ldloc.0
        brtrue.s notnull1
        ldstr "Default"
        br.s isnull1
        notnull1: ldloc.0
        isnull1: stloc.1
    

    而后者看起来像这样:

    // y = s ?? "Default";
        ldloc.0
        dup
        brtrue.s notnull2
        pop
        ldstr "Default"
        notnull2: stloc.1
    

    基于此,我会说空合并运算符针对其目的进行了优化,而不仅仅是语法糖。

    【讨论】:

      【解决方案12】:

      在将字符串解析为其他数据类型时,我经常使用 null-coalescing 运算符。我有一组扩展方法,它们包装 Int32.TryParse 之类的东西并返回一个可为空的 int,而不是将输出参数暴露给我的代码的其余部分。然后我可以在一行代码中解析一个字符串并在解析失败时提供一个默认值。

      // ToNullableInt(), is an extension method on string that returns null
      // if the input is null and returns null if Int32.TryParse fails.
      
      int startPage = Request.QueryString["start"].ToNullableInt() ?? 0;
      

      【讨论】:

        【解决方案13】:

        我一直在使用它与 App.Config 的东西

        string somethingsweet = ConfigurationManager.AppSettings["somesetting"] ?? "sugar";
        

        【讨论】:

          猜你喜欢
          • 2011-05-10
          • 2011-07-16
          • 1970-01-01
          • 2023-03-10
          • 1970-01-01
          • 2011-12-10
          • 2014-08-09
          • 2014-08-05
          • 2022-11-01
          相关资源
          最近更新 更多