Нулевой условный оператор для "обнуления" существования элемента массива

Новый оператор с нулевым условием C# 6.0 - это удобный инструмент для написания более сжатого и менее запутанного кода. Предполагая, что у одного есть массив клиентов, вы можете получить значение null вместо длины, если customers является нулевым, используя это (примеры из MSDN):

int? length = customers?.Length;

Точно так же вы можете получить null вместо клиента с этим:

Customer first = customers?[0];

И для более сложного выражения, это дает нуль, если customers является нулевым, первый клиент является нулевым, или первый клиент Orders объект нулевой:

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

Но есть и интересный случай несуществующего клиента, который, по-видимому, не обращается к нулевому условному оператору. Выше мы видели, что нулевой клиент покрыт, т.е. если запись в customers массив равен нулю. Но это совершенно не похоже на несуществующего клиента, например, поиск клиента 5 в 3-х элементном массиве или клиенте n в списке из 0 элементов. (Обратите внимание, что то же обсуждение относится и к поиску по словарю.)

Мне кажется, что условно-нулевой оператор ориентирован исключительно на отрицание эффектов исключения NullReferenceException; IndexOutOfRangeException или KeyNotFoundException находятся в одиночестве, обнажены, сжимаются в углу и им нужно постоять за себя! Я утверждаю, что в духе нуль-условного оператора он должен быть в состоянии обрабатывать и эти случаи... что приводит к моему вопросу.

Я пропустил это? Предоставляет ли null-conditional какой-либо элегантный способ действительно охватить это выражение...

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

... когда нет нулевого элемента?

3 ответа

Решение

Нет, потому что это нулевой условный оператор, а не indexoutofrange -условный оператор и является просто синтаксическим сахаром для чего-то вроде следующего:

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

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

Вы можете видеть, что если нет нулевого клиента, вы получите свой постоянный IndexOutOfRangeException,

Один из способов обойти это - использовать метод расширения, который проверяет индекс и возвращает ноль, если он не существует:

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

Тогда ваш чек может быть:

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

Нет нуля, нет проблем.

Если вы хотите получить n-й элемент без исключений NullReference или IndexOutOfRange, вы можете использовать:

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

Он не поддерживает безопасность индексации, потому что, когда вы приступаете к этому, индексатор действительно является просто синтаксическим сахаром для любого другого типа метода.

Например:

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, а не исключение нулевой ссылки, которое было бы выдано, если босс был нулевым.

Та же самая проблемная проблема возникнет, если мы поймаем исключения поиска по индексу, исключения ключа не найдены и т. Д.

Другие вопросы по тегам