What is double exclamation mark in C#?
Asked Answered
S

3

82

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

public virtual bool ContainsKey(object key!!)

It looks like two null-forgiving operators. Is there a document about it?

Stagy answered 10/2, 2022 at 10:47 Comment(2)
It appears 9 times in that Hashtable.cs - that wasn't done by accident / typoDannie
Since T? is a long-established notation for nullable types, and ! is the NOT operator, the logical syntax for a not-null constraint should be object!? key. But no, they had to pick something more confusing.Oxheart
P
113

This is a null-parameter check syntax which was going to be introduced in C# 11. This proposal has since been rolled back following community feedback.

The proposal is here, and the PR doing a first roll-out to the runtime is here.

The syntax:

public void Foo(string bar!!)
{
}

Is roughly equivalent to:

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

... although the actual implementation uses a throw helper, something like:

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.

Methods containing throw statements are less likely to be inlined by the JIT, so using a throw helper makes it more likely that your method can be inlined, which might remove the null-check altogether! See on SharpLab.

Note that the use of !! is an implementation detail of your method: it just causes the compiler to insert code which you could have written yourself anyway. This means that moving from throw to !! (or vice versa) is not a breaking change.


There are a couple of places where !! will get the compiler to generate code which you can't (easily) write by hand, however.

One place where !! is particularly useful is in records with primary constructors. For example:

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

In order to correctly null-check the Name parameter in previous versions of C#, you have to write this out longhand:

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

Another place where !! does something which you can't write yourself is in chained constructor calls:

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

This null-checks s before accessing s.Length, something like this (which isn't valid C#):

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

See on SharpLab.

Another interesting aspect is that the null-checks are inserted before field assignments in constructors. For example:

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

Is compiled as:

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

That is, the null-check happens before the instantiation of ExpensiveObject. See on SharpLab.

Propagate answered 10/2, 2022 at 10:53 Comment(1)
The next logical steps are string bar!!!, where every assignment to this variable is followed by a corresponding autogenerated if-statement to check for null, and then string bar!!!! where every assignment is pre-checked if it would try to assign null to it, and then string bar!!!!! where even the values that are attempted assigned to the variable will generate a warning if there is a slight chance it could possibly, potentially, mayhaps, be null. Wonder what 6 exclamation marks would mean, I'm sure they'll think of something.Limewater
O
7

According to the latest updates published by microsoft about c# 11, this feature seems to have been removed.

Source : 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.

Occlusive answered 1/5, 2022 at 22:12 Comment(0)
S
2

The New C# 11 Parameter Null Checking Feature !!

The double exclamation mark !! is a parameter null checking feature that is replacing the following (older) null checking:

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

With this new way null check, the same code is much shorter:

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

Both of these methods (and the next one) throws an ArgumentNullException if the parameter is null

Elaboration

Basically, using this code you are making sure that object arg is not null

Another Way: ArgumentNullException.ThrowIfNull(parameter)

edit: this static method is introduced in C# 10 (.NET 6) and is dependent on .NET 6 version

Simplified Code Example:

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

With the latest PR instructions, this method can be used in places the !! is not possible:

ArgumentNullException.ThrowIfNull is used where !! isn't possible, but the method call is

The following PR page update states that !! for now can be used only in method arguments, although the C# language team is considering to add this operator to properties, locals and arbitrary expressions:

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)

Expanded code example where !! is not available (for the variable we want to check for 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);
            }
        }
    }

Output:

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

Test this code here

Surefire answered 16/2, 2022 at 15:15 Comment(11)
I'm not sure that adding a new answer 6 days later helps much? Also ArgumentNullException.ThrowIfNull is not dependent on the C# version, only the .NET version.Propagate
Thank you for your feedback @canton7, any elaboration with references upon an existing answer and a hot topic is welcome because as more information we have as developers community, the better we grow. Also, About ArgumentNullException.ThrowIfNull, it is dependent only on .NET version and I just made an edit to emphasize this, thank you.Surefire
I think the last code example is missleading as it is posted directly under the explanation With the latest PR instructions, this method is used in places the !! is not possible:. But in this code fragment !! would be possible. I would suggest changing the example to one where !! is not possible.Legere
Hi, @MarkusSafar I have added an example to your request. The example is where !! is not possible, that is inside the methodSurefire
I am not sure this example fits the purpose of the !! operator as you just show how to use ArgumentNullException.ThrowIfNull(). You do not really show how !! is useful here.Legere
@MarkusSafar this example shows the usage of ArgumentNullException.ThrowIfNull() this is why in this specific example I have integrated !! as a thought experiment for a must-have userId to call a pseudo API request to demonstrate the unavailability of !! inside a method.Surefire
Makes no sense to me as the example would compile and function just fine without !!, right? Or don't I see the point here?Legere
The point is to give a live example to ArgumentNullException.ThrowIfNull() the !! doesn't matter in this example. Although, to correct your point of view, the !! does affect the function in the following way: if the method is called with null userId you will have an ArgumentNullExceptionSurefire
That's the point. The question and your answer is about the !! operator. So your example should show exactly that and not something else. And I disagree with your statement (if the method is called with null userId you will have an ArgumentNullException) as userId is never used in your example.Legere
@MarkusSafar this example requires imagination for the !! part (think it's there) because as I mentioned, it is written as pseudo because the point here is to explore and show another way to handle parameter null checking because it may benefit othersSurefire
But don't you think you should also explain in an example how to make use of the !! operator properly? I don't see any example where it's use is required or is beneficial.Legere

© 2022 - 2024 — McMap. All rights reserved.