【问题标题】:What is double exclamation mark in C#?C#中的双感叹号是什么?
【发布时间】:2022-05-02 06:12:29
【问题描述】:

来自https://source.dot.net/#System.Private.CoreLib/Hashtable.cs,475

public virtual bool ContainsKey(object key!!)

它看起来像两个容错运算符。有相关文件吗?

【问题讨论】:

  • 它在Hashtable.cs 中出现了 9 次 - 这不是偶然/错字
  • 还记得 C# 团队声称与 C++ 相比语法更简洁的那一天吗?那些日子已经一去不复返了,因为语法的复杂性自 C# 4 以来呈指数级增长。!! 是雪球效应的结果,当需要引入新语法来解决以前的语法添加引入的问题时, ! 语法 不再只是 not 运算符了。
  • 由于 T? 是可空类型的长期使用符号,而 ! 是 NOT 运算符,非空约束的逻辑语法应为 object!? key。但是不,他们必须选择更令人困惑的东西。
  • 期待将我的非空重量关税票价对象缩写为 WTF!!。 XD
  • @JohnAlexiou C# 自 2.0 中的可空值类型以来一直不必要地引入新语法,因此这并不是什么新东西。 3.0 尤其大,因为它添加了自动属性、扩展方法、var、初始化程序甚至 linq 查询,以及许多其他添加,而不仅仅是语法糖。问题是,即使有所有不必要的语法,它看起来仍然比 C++ 更干净。可能是因为这些微不足道的添加减少了样板文件,这使得阅读代码更加切题,减少了噪音。

标签: c# function syntax method-signature c#-11.0


【解决方案1】:

这是一个将在 C# 11 中引入的空参数检查语法。此提案有 since been rolled back 遵循社区反馈。

提案 is here,以及 PR 首次向运行时推出 is here

语法:

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);
        }
    }
}

See on SharpLab.

包含throw 语句的方法不太可能被 JIT 内联,因此使用 throw 帮助器更有可能内联您的方法,这可能会完全消除空检查! See on SharpLab.

请注意,!! 的使用是您方法的实现细节:它只会导致编译器插入您本来可以自己编写的代码。这意味着从 throw 移动到 !!(反之亦然)并不是一个重大变化。


有几个地方!! 会让编译器生成您不能(轻松)手动编写的代码。

!! 特别有用的一个地方是在具有主构造函数的记录中。例如:

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

为了在以前的 C# 版本中正确地对 Name 参数进行空值检查,您必须将其写出来:

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.Length 之前,这个空值检查 s,类似这样(这不是有效的 C#):

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

See on SharpLab.

另一个有趣的方面是在构造函数中的字段分配之前插入空检查。例如:

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 的实例化之前。 See on SharpLab.

【讨论】:

  • 我不是在争论,我只是简单地评论一下。由一个类似的问题提示,该问题刚刚作为此副本关闭
  • 接下来的逻辑步骤是string bar!!!,其中对该变量的每个赋值都后跟一个相应的自动生成的 if 语句来检查 null,然后是 string bar!!!!,其中每个赋值都预先检查 if它会尝试将null 分配给它,然后string bar!!!!! 在其中即使尝试分配给变量的值也会生成一个警告,如果它有可能是null。不知道 6 个感叹号是什么意思,我相信他们会想到一些东西。
【解决方案2】:

新的 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...
}

如果参数为null,则这两种方法(以及下一种方法)都会抛出ArgumentNullException

细化

基本上,使用此代码可以确保 object arg 不是 null

另一种方式:ArgumentNullException.ThrowIfNull(parameter)

编辑: 这个静态方法是在 C# 10 (.NET 6) 中引入的,并且依赖于 .NET 6 版本

简化代码示例:

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

有了最新的 PR 说明,这个方法可以用在!! 不可能的地方:

ArgumentNullException.ThrowIfNull 用于!! 不可能的情况,但方法调用是

以下 PR 页面更新指出 !! 目前只能在方法参数中使用,尽管 C# 语言团队正在考虑将此运算符添加到属性、局部变量和任意表达式:

请注意!!目前仅限于应用于方法参数, 因为这是绝大多数用例(如更改所示 在此 PR 中制作)

!! 不可用的扩展代码示例(对于我们要检查是否为空的变量):

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

测试这段代码here

【讨论】:

  • 我不确定 6 天后添加新答案有多大帮助?此外ArgumentNullException.ThrowIfNull 不依赖于 C# 版本,仅依赖于 .NET 版本。
  • 感谢您的反馈@canton7,欢迎对现有答案和热门话题进行任何详细说明,因为我们作为开发者社区拥有的信息越多,我们的发展就越好。另外,关于ArgumentNullException.ThrowIfNull,它只依赖于.NET版本,我只是做了一个编辑来强调这一点,谢谢。
  • 我认为最后一个代码示例具有误导性,因为它直接发布在说明下 使用最新的 PR 说明,此方法用于无法使用 !! 的地方:。但是在这个代码片段中!! 是可能的。我建议将示例更改为无法使用 !! 的示例。
  • 嗨,@MarkusSafar 我在您的请求中添加了一个示例。例子在哪里!!是不可能的,就是在方法里面
  • 我不确定这个示例是否符合 !! 运算符的目的,因为您只是展示了如何使用 ArgumentNullException.ThrowIfNull()。您并没有真正展示 !! 在这里的用处。
【解决方案3】:

根据微软发布的关于c#11的最新更新,这个功能似乎已经被移除了。

来源:https://devblogs.microsoft.com/dotnet/csharp-11-preview-updates/#remove-parameter-null-checking-from-c-11

我们尽早预览了参数空值检查,因为我们 预期的反馈。此功能允许!在参数的末尾 在方法开始之前提供参数空检查的名称 执行。我们在 C# 11 早期就包含了这个特性,以最大化 反馈,我们从 GitHub cmets、MVP、社交媒体、 会议观众、与用户的个人对话以及 C# 设计团队的持续反思。我们收到了广泛的反馈 关于这个功能,我们很感激。

我们从中获得的反馈和广泛的见解 反馈使我们重新考虑将此作为 C# 11 功能。我们没有 有足够的信心认为这是 C# 的正确功能设计,并且 正在从 C# 11 中删除它。我们可能会再次回到这个区域 以后的日期。

【讨论】:

    猜你喜欢
    • 2011-01-11
    • 1970-01-01
    • 2011-12-22
    • 2018-11-14
    • 1970-01-01
    • 2011-11-19
    • 1970-01-01
    • 2020-05-03
    • 2012-03-06
    相关资源
    最近更新 更多