【问题标题】:Null conditional operator to "nullify" array element existence空条件运算符“取消”数组元素的存在
【发布时间】:2016-08-30 15:18:25
【问题描述】:

新的 C# 6.0 空条件运算符是编写更简洁和更少复杂代码的便捷工具。假设一个客户有一组客户,那么如果 customers 为空,则您可以获得空而不是长度(来自 MSDN 的示例):

int? length = customers?.Length;

同样,您可以通过以下方式获得 null 而不是客户:

Customer first = customers?[0];

对于更详细的表达式,如果customers 为空,第一个客户为空,或者第一个客户的Orders 对象为空,则返回空:

int? count = customers?[0]?.Orders?.Count();

但是还有一个有趣的案例,即不存在的客户,空条件运算符似乎没有解决。我们在上面看到一个 null 客户被覆盖,即如果 customers 数组中的条目为空。但这与 不存在 的客户截然不同,例如在 3 元素数组中查找客户 5 或在 0 元素列表中查找客户 n。 (请注意,同样的讨论也适用于字典查找。)

在我看来,空条件运算符只专注于否定 NullReferenceException 的影响; IndexOutOfRangeException 或 KeyNotFoundException 是孤独的,暴露的,蜷缩在角落里,需要自生自灭!我认为,本着空条件运算符的精神,它也应该能够处理这些情况......这导致了我的问题。

我错过了吗?空条件是否提供任何优雅的方式来真正覆盖这个表达式...

customers?[0]?.Orders?.Count();

...当没有第零个元素时?

【问题讨论】:

  • “空条件运算符”,而不是“异常通配符”。就我个人而言,如果它绕过 OutOfIndexExceptions/KeyNotFoundException for nulls,我会考虑不直观的行为。
  • ?. 运算符只是将代码包装在if (val != null) { ...//Continue } 中。 IndexOutOfRangeException 可以来自任何地方,而且许多课程确实会抛出它。就传统数组而言,索引操作也不一定意味着索引查找,它也用于字典/查找/等。捕获这种异常是没有意义的。

标签: c# arrays null c#-6.0 null-propagation-operator


【解决方案1】:

不,因为它是一个 null 条件运算符,而不是 indexoutofrange 条件运算符,并且只是类似于以下内容的语法糖:

int? count = customers?[0]?.Orders?.Count();

if (customers != null && customers[0] != null && customers[0].Orders != null)
{
    int count = customers[0].Orders.Count();
}

您可以看到,如果没有第零个客户,您将获得您的常规IndexOutOfRangeException

您可以解决它的一种方法是使用扩展方法来检查索引并在它不存在时返回 null:

public static Customer? GetCustomer(this List<Customer> customers, int index)
{
    return customers.ElementAtOrDefault(index); // using System.Linq
}

那么你的支票可能是:

int? count = customers?.GetCustomer(0)?.Orders?.Count();

【讨论】:

  • 我实际上最终在Dictionary&lt;TKey, TValue&gt; 上使用了一个名为ElementAtOrNull 的扩展方法,它通过返回dictionary.ContainsKey(lookupValue) ? dictionary[lookupValue] : null 以类似的方式运行
  • 将 ElementAtOrDefault 信息移近答案顶部可能会更好。它看起来像是一个标准功能,并且完全符合用户的需求。
【解决方案2】:

它不支持索引安全性,因为当您开始使用它时,索引器实际上只是任何其他类型方法的语法糖。

例如:

public class MyBadArray
{
    public Customer this[int a]
    {
        get
        {
            throw new OutOfMemoryException();
        }
    }
}

var customers = new MyBadArray(); 
int? count = customers?[5]?.Orders?.Count();

应该在这里抓到吗?如果异常更合理,类似于 KeyNotFoundException,但特定于我们正在实现的集合类型怎么办?我们必须不断更新?. 功能才能跟上。

此外,?. 不会捕获异常。它可以阻止它们。

var customer = customers?[5];实际上编译为:

Customer customer = null;
if (customers != null)
    customer = customers[5];

让它捕获异常变得异常困难。例如:

void Main()
{
    var thing = new MyBadThing(); 
    thing.GetBoss()?.FireSomeone();
}

public class MyBadThing
{
    public class Boss
    {
        public void FireSomeone() 
        { 
            throw new NullReferenceException();
        }
    }
    public Boss GetBoss()
    {
        return new Boss();
    }
}

如果它只是简单地捕获异常,它会写成:

Boss boss = customer.GetBoss();
try 
{
    boss.FireSomeone();
} catch (NullReferenceException ex) { 

}

这实际上会在FireSomeone 中捕获异常,而不是在boss 为空时抛出的空引用异常。

如果我们要捕获索引查找异常、未找到键异常等,也会出现同样的错误捕获问题。

【讨论】:

  • 使用 ArgumentOutRange 异常覆盖操作符 [] 应该可以工作。
【解决方案3】:
customers?.FirstOrDefault()?.Orders?.Count();

没有零点,没问题。

【讨论】:

  • 不要害怕零世界。
  • 您找到了将 [0] 替换为“FirstOrDefault”的解决方案,但问题更普遍,关于 [index],其中 index 可能超出数组范围..
【解决方案4】:

如果你想在没有 NullReference 或 IndexOutOfRange 异常的情况下获取第 n 个元素,你可以使用:

customers?.Skip(n)?.FirstOrDefault()

【讨论】:

  • 这是迄今为止最优雅的解决方案
猜你喜欢
  • 2019-02-09
  • 2017-08-25
  • 2011-11-02
  • 1970-01-01
  • 2017-12-01
相关资源
最近更新 更多