C#中的双感叹号是什么?

What is double exclamation mark in C#?

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

public virtual bool ContainsKey(object key!!)

它看起来像两个 null-forgiving 运算符。有相关文档吗?

这是一个 null-parameter 检查语法,将在 C# 11 中引入。此提议有 since been rolled back 社区反馈。

提案is here, and the PR doing a first roll-out to the runtime is here.

语法:

public void Foo(string bar!!)
{
}

大致相当于:

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

...虽然实际实现使用了一个 throw helper,比如:

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 helper 使您的方法更有可能被内联,这可能会完全删除 null-check! See on SharpLab.

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


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

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

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

为了正确 null-check 以前版本的 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) { }
}

This null-checks s 在访问 s.Length 之前,像这样(这不是有效的 C#):

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

See on SharpLab.

另一个有趣的方面是 null-check 被插入到构造函数中的字段赋值之前。例如:

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

也就是说,null-check 发生在 ExpensiveObject 实例化之前。 See on SharpLab.

新的 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 is used where !! isn't possible, but the method call is

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

Note that !! is currently limited to only apply to method arguments, as that's the vast majority use case (as is exemplified by the changes made in this 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

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

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

We previewed parameter null-checking as early as possible because we anticipated feedback. This feature allows !! on the end of a parameter name to provide parameter null checking before the method begins execution. We included this feature early in C# 11 to maximize feedback, which we gathered from GitHub comments, MVPs, social media, a conference audience, individual conversations with users, and the C# design team’s ongoing reflection. We received a wide range of feedback on this feature, and we appreciate all of it.

The feedback and the wide range of insight we gained from this feedback led us to reconsider this as a C# 11 feature. We do not have sufficient confidence that this is the right feature design for C# and are removing it from C# 11. We may return to this area again at a later date.