Что такое двойной восклицательный знак в C#?

Из https://source.dot.net/#System.Private.CoreLib/Hashtable.cs,475:

      public virtual bool ContainsKey(object key!!)

Это похоже на два оператора, прощающих null. Есть документ об этом?

3 ответа

Это синтаксис проверки нулевого параметра, представленный в C# 11.

Предложение находится здесь , а PR, выполняющий первое развертывание в среде выполнения , — здесь .

Синтаксис:

      public void Foo(string bar!!)
{
}

Примерно эквивалентно:

      public void Foo(string bar)
{
    if (bar is null)
    {
        throw new ArgumentNullException(nameof(bar));
    }
}

... хотя в реальной реализации используется хелпер throw, что-то вроде:

      public void Foo(string bar)
{
    <PrivateImplementationDetails>.ThrowIfNull(bar, "bar");
}

[CompilerGenerated]
internal sealed class <PrivateImplementationDetails>
{
    internal static void Throw(string paramName)
    {
        throw new ArgumentNullException(paramName);
    }

    internal static void ThrowIfNull(object argument, string paramName)
    {
        if (argument == null)
        {
            Throw(paramName);
        }
    }
}

См. на SharpLab.

Методы, содержащие операторы, с меньшей вероятностью будут встроены JIT, поэтому использование вспомогательного метода throw повышает вероятность того, что ваш метод может быть встроен, что может полностью удалить проверку нуля! См. на SharpLab.

Обратите внимание, что использование является деталью реализации вашего метода: оно просто заставляет компилятор вставлять код, который вы все равно могли бы написать самостоятельно. Это означает, что переход от throwto (или наоборот) не является критическим изменением.


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

Одно место, где это особенно полезно, — это записи с первичными конструкторами. Например:

      public record Person(string Name!!, int Age);

Для того, чтобы правильно обнулить Nameпараметр в предыдущих версиях C#, вы должны написать это от руки:

      public record Person
{
    public string Name { get; init; }
    public int Age { get; init; }
    
    public Person(string name, int age)
    {
        if (name is null)
            throw new ArgumentNullException(nameof(name));
        (Name, Age) = (name, age);
    }
    
    public void Deconstruct(out string name, out int age) =>
        (name, age) = (Name, Age);
}

Еще одно место, где !!делает что-то, что вы не можете написать сами, в цепных вызовах конструктора:

      public class C
{
    public C(int i) { }
    public C(string s!!) : this(s.Length) { }
}

Эта нулевая проверка sперед доступом s.Length, что-то вроде этого (что недопустимо для С#):

      public C(string s)
{
    if (s is null)
        throw new ArgumentNullException(nameof(s));
    C(s.Length);
}

См. на SharpLab.

Еще один интересный аспект заключается в том, что проверки на null вставляются перед присваиванием полей в конструкторах. Например:

      public class C
{
    private readonly ExpensiveObject e = new ExpensiveObject();
    public C(string s!!) { }
}

Собирается как:

      public class C
{
    private readonly ExpensiveObject e;
    public C(string s)
    {
        if (s is null)
            throw new ArgumentNullException(nameof(s));
        e = new ExpensiveObject();
    }
}

То есть проверка нуля происходит до создания экземпляра ExpensiveObject. См. на SharpLab.

Согласно последним обновлениям, опубликованным Microsoft о С# 11, эта функция, похоже, была удалена.

Источник: https://devblogs.microsoft.com/dotnet/csharp-11-preview-updates/#remove-parameter-null-checking-from-c-11 .

Мы сделали предварительный просмотр проверки нулевого значения параметра как можно раньше, потому что ожидали обратной связи. Эта функция позволяет !! в конце имени параметра, чтобы обеспечить проверку нулевого значения параметра перед началом выполнения метода. Мы включили эту функцию в C# 11 на раннем этапе, чтобы максимизировать обратную связь, которую мы собрали из комментариев GitHub, MVP, социальных сетей, аудитории конференции, индивидуальных бесед с пользователями и постоянных размышлений команды разработчиков C#. Мы получили множество отзывов об этой функции и ценим их все.

Отзывы и широкий спектр идей, которые мы получили из этих отзывов, заставили нас пересмотреть это как функцию C # 11. У нас нет достаточной уверенности в том, что это правильный дизайн функции для C#, и мы удаляем ее из C# 11. Мы можем вернуться к этой области позже.

Новая функция проверки нулевого значения параметра C# 11

Двойной восклицательный знак — это функция проверки нулевого значения параметра , которая заменяет следующую (более старую) проверку нулевого значения:

      void Bar(object arg)
{
    if (arg is null)
    {
        throw new ArgumentNullException(nameof(arg));
    }
    Console.WriteLine("Hi");
    // use arg...
}

Благодаря этому новому способу проверки нуля тот же код стал намного короче:

      void Bar(object arg!!)
{
    Console.WriteLine("Hi");
    // use arg...
}

Оба этих метода (и следующий) выдаютArgumentNullExceptionесли параметр

Разработка

По сути, используя этот код, вы убедитесь, что object argне является null

Другой путь:ArgumentNullException.ThrowIfNull(parameter)

редактировать: этот статический метод представлен в С# 10 (.NET 6) и зависит от версии .NET 6

Пример упрощенного кода:

      void Greeting(string name)
{
    ArgumentNullException.ThrowIfNull(name);
    Console.WriteLine($"Hi {name}");
}

С последними инструкциями PR этот метод можно использовать в местах, где это невозможно:

ArgumentNullException.ThrowIfNull используется там, где это невозможно, но вызов метода

В следующем обновлении страницы PR говорится, что на данный момент его можно использовать только в аргументах метода, хотя языковая группа C# рассматривает возможность добавления этого оператора в свойства, локальные переменные и произвольные выражения:

Обратите внимание, что !! в настоящее время ограничено применением только к аргументам метода, так как это подавляющее большинство вариантов использования (о чем свидетельствуют изменения, внесенные в этот PR)

Пример расширенного кода, где !!недоступен (для переменной, которую мы хотим проверить на значение null):

      void GetUserCities(string userId!!)
    {
        // suppose you got this data (that is inconsistent and may contain a null)
        // from an API request by the userId
        var cities = new Dictionary<string, string>(){
            {"UK", "London"},
            {"USA", "New York"},
            {"India", "New Delhi"},
            {"Wakanda", null},
        };
        foreach(var pair in cities) {
            try {
                ArgumentNullException.ThrowIfNull(pair.Value);
                Console.WriteLine("Country: " + pair.Key + ", City:" + pair.Value);
            } catch(System.ArgumentNullException) {
                Console.WriteLine("Could not find a city for this country: " + pair.Key);
            }
        }
    }

Выход:

       Country: UK, City:London
Country: USA, City:New York
Country: India, City:New Delhi
Could not find a city for this country: Wakanda

Протестируйте этот код здесь

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