"nullify" 数组元素存在的空条件运算符

Null conditional operator to "nullify" array element existence

新的 C# 6.0 空条件运算符是编写更简洁、更简单的代码的便捷工具。假设一个客户有一组客户,那么如果 customers 为 null 使用此(来自 MSDN 的示例):

,那么您可以获得 null 而不是长度
int? length = customers?.Length;

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

Customer first = customers?[0];

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

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

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

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

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

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

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

不,因为它是一个 null- 条件运算符,而不是 indexoutofrange- 条件运算符并且只是语法糖以下:

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

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

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

您可以解决它的一种方法是使用一个扩展方法来检查索引,如果索引不存在则 returns 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();

它不支持索引安全,因为当你认真对待它时,索引器实际上只是任何其他类型方法的语法糖。

例如:

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 为空则抛出的空引用异常。

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

customers?.FirstOrDefault()?.Orders?.Count();

没有zeroeth,没问题。

如果你想获取第n个元素而不出现NullReference或IndexOutOfRange异常,你可以使用:

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