【问题标题】:Is there any way to perform a sort of null coalesce on a property in C#?有没有办法对 C# 中的属性执行某种空合并?
【发布时间】:2014-02-27 08:42:47
【问题描述】:

我不太清楚如何表达这个问题,但场景如下:

假设我有以下课程:

public class SampleClass
{
    public int Number { get; set; }
}

我知道你可以空合并包含的类:

SampleClass newSampleClass = possibleNullSampleClass ?? notNullSampleClass;

有没有办法对属性执行某种空合并,所以我不必这样做:

int? num = sampleClass != null ? new int?(sampleClass.Number) : 5;

似乎有类似??? 运算符来执行此检查非常有用,所以我可以这样做:

int? num = sampleClass.Number ??? 5;

在 C# 中有类似的可能吗?

【问题讨论】:

  • 这种操作有名称或术语吗?是否计划用于任何未来版本的 C#?拥有它似乎非常好。
  • 这通常被称为“存在运算符”,在 Coffeescript 和少数其他语言中都可以找到。它通常表示为?. 例如SampleClass?.Number
  • 一个相关的概念也是空对象模式。例如,在 Objective-C 中,您可以简单地编写 sampleClass.Number 的等价物,它不会抛出异常——您会得到一个确定的值。
  • 虽然我不确定 Objective-C 与空对象模式有什么关系,但我认为它是比以下答案中的空值检查更好的解决方案。

标签: c# syntax null-coalescing-operator


【解决方案1】:

没有现有的运营商可以做到这一点。您已经展示了一种完全可以接受的处理问题的方法。

如果你愿意,你可以将逻辑封装在一个方法中,但我不确定这是否真的值得付出努力:

public static TResult Access<TSource, TResult>(
    TSource obj, Func<TSource, TResult> selector, TResult defaultIfNull)
    where TSource : class
{
    if (obj == null)
        return defaultIfNull;
    else
        return selector(obj);
}

SampleClass sampleClass = null;
int num = Access(sampleClass, s => s.Number, 5);

(如果您愿意,您也可以考虑将其作为扩展方法,尽管我个人对所有类的扩展方法持谨慎态度。也可以考虑其他名称,例如 Use。)

【讨论】:

    【解决方案2】:

    目前没有这样的运营商。但在 C# 6 中会出现“安全导航”操作符,您将能够编写

    int number = sampleClass?.Number;
    

    即使sampleClass 等于null,此代码也不会因 NullRef 而失败。现在您可以使用以下扩展方法从对象中提取值并封装空检查:

    public static TResult Maybe<TSource, TResult>(
        this TSource source, Func<TSource, TResult> produceResult, Func<TResult> produceDefault)
        where TSource : class
    {
        return source == null ? produceDefault() : produceResult(source);
    }
    

    并按如下方式使用:

    int numberOrFive = sampleClass.Maybe(c => c.Number, () => 5);
    

    【讨论】:

    • 非常酷,我期待 C# 6。我可能也喜欢这种扩展方法,但我不确定我是否想通过扩展方法增加混乱。
    • 有时扩展方法有助于编写更简短和声明性的代码。您可以比较两种实现相同但编写方式不同的方法:gist.github.com/takemyoxygen/6501402
    【解决方案3】:

    我将提议修改 Servy 的帖子。我认为如果他的方法能够处理一般情况(对象链上的任何地方都可能存在空值),他的方法将证明是有用的。

    public static TResult Access<TSource, TResult>(
       TSource obj, Func<TSource, TResult> selector, TResult defaultIfNull)
       where TSource : class
    {
        TResult result;
        try
        {
            result = selector(obj);
        }
        catch ( NullReferenceException)
        {
            result = defaultIfNull;
        }
    
        return result;
    
    }
    

    如果您尝试访问,请使用这种方式 邮编 = 客户.地址.邮编;

    如果客户或地址为空,您将受到保护

    【讨论】:

    • 您不应该对控制流使用异常。如果您有一个对象链,您只需将每个级别的此方法的调用链接起来,以保持整个深度的安全。那就是说;拥有一个所有值都可能是null 的调用链是您通常不应该将自己置于开始的情况;你可能在不应该使用的时候使用null
    • 能否提出另一种方法来解决在不捕获异常的情况下防止对象引用上下空引用问题的问题?
    • 当然,正如我所说,您将调用链接到此方法。
    • 空对象模式 - 但这取决于您要解决的问题。
    • 再次,请参阅我的第一条评论。如果你把自己放在那个位置上,你几乎肯定会遇到设计问题。例如,在这种情况下,Address 永远不应为空,因此您无需编写代码使其为空。
    【解决方案4】:

    你不能在 C# 中做到这一点。您需要明确测试无效性:

    MyWidget x ;
    
    int? v = x == null ?  (int?)x.SomeProperty : (int?) null ;
    

    如果您真的认为它有用,您可以在 Microsoft Connect 上记录您的建议:

    http://connect.microsoft.com/VisualStudio
    

    我愿意成为他们考虑过的,并认为它在语言中没有足够的用处。

    【讨论】:

      【解决方案5】:

      你完全可以这样做:

      var x = (myObject ?? DefaultObject).Field;
      

      还有这个……

      var x = (myObject == null) ? myObject.Field : defaultValue;
      

      【讨论】:

      • 这假设您有一个DefaultObject,它既不为空,又具有给定字段的合理默认值。您不是在创建这样的对象,而这正是工作所在。在某些情况下,您可能还无法能够构造对象、影响其字段值将返回的内容,或者创建实例的成本可能过高。
      • 这些都不是问题的要求。如果这些事情很重要,你必须提到它。我经常这样做。使用扩展方法接受的答案比这更易读。
      • 1) 无论如何,您仍然需要构造对象,给解决方案带来一种错误的简洁感 2) 您将其断言为 一般情况 的解决方案,而不是一个特定的案例,因此您需要为所有这些问题在某个时候或另一个时候出现做好准备。
      猜你喜欢
      • 2021-12-23
      • 2014-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多