Условный оператор Null для "уничтожения" существования элемента массива

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

int? length = customers?.Length;

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

Customer first = customers?[0];

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

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

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

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

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

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

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

Ответ 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();

Ответ 2

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

Нет нулей, никаких проблем.

Ответ 3

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

Например:

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 был null.

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

Ответ 4

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

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