2

Recently I saw such code in a .Net Core 6 project:

persons = persons.Where(c => c is { Id: { } }).ToArray()

I assumed it is "is" operator pattern in C# 9 and tried to understand how I can read it, but could not explain what it does with words.

Based on the assumptions on what the code is doing, I managed to write a test for it, so, it looks like it checks c for null and Id for null as well.

How I should approach reading such syntax?

The test for the logic (Xunit, FluentAssertion)

record TestPerson(int? Id, string Name);
        
[Fact]
public void TestStrangeSyntax()
{
    var persons = new TestPerson[] { new TestPerson(1, "John"), null, new TestPerson(null, "Jane") };
    persons = persons.Where(c => c is { Id: { } }).ToArray();

    persons.Should().BeEquivalentTo(new [] { persons[0] });
}

Edit: I tried to change the code in Where to

c => c != null && c.Id !=null

and my IDE suggested to convert it to the pattern. Since we both use the same IDE, I assume this was what the person did.

After I tried to change it to

c?.Id != null

no refactor was suggested.

Also, after reading the answers from @Panagiotis Kanavos and @Orace I agree it can be treated as a duplicate.

Mykola
  • 197
  • 1
  • 10
  • 1
    Looks like someone got too clever with pattern matching. The same could be written as `c?.Id !=null`. And even `?.` can be removed if `persons` isn't supposed to contain nulls: `c.Id != null`. – Panagiotis Kanavos Mar 28 '23 at 09:44
  • 2
    Is this actual code or an interview test? If it's actual code, I have thoughts about the author – Panagiotis Kanavos Mar 28 '23 at 09:46
  • It is an actual code. Do you think I should refactor it to readable (by me) c?.Id != null? Or this is the new approach and devs should be expected to read it freely - like linq for example? – Mykola Mar 28 '23 at 11:03

2 Answers2

4

This is pattern matching syntax. It matches an object ({}) with a non-null property Id. It's the too clever equivalent of c=>c?.Id !=null.

The is operator matches the argument against the pattern on the right. It can be used to match against specific types, properties and values. Used properly, it can lead to far cleaner code, for example :

static bool IsFirstFridayOfOctober(DateTime date) =>
    date is { Month: 10, Day: <=7, DayOfWeek: DayOfWeek.Friday };

Or

public decimal CalculateDiscount(Order order) =>
    order switch
    {
        { Items: > 10, Cost: > 1000.00m } => 0.10m,
        { Items: > 5, Cost: > 500.00m } => 0.05m,
        { Cost: > 250.00m } => 0.02m,
        null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
        var someObject => 0m,
    };

In .NET 6, where Nullable Reference Types are enabled by default in new projects, the null check wouldn't be needed at all if Id wasn't nullable to begin with. All the statements in this snippet are non-nullable :

record TestPerson(int Id, string Name);
var persons = new TestPerson[] { ... };

Adding an explicit null to the array would generate a warning.

var persons = new TestPerson[] { new TestPerson(1, "John"), null};

This emits :

warning CS8625: Cannot convert null literal to non-nullable ref erence type.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Thanks for your great answer. Please see my edit - I assume, the IDE suggested the change. I will keep it there for the next dev, as, although I did read articles on pattern matching, it evaded my consciousnesses and I never used it. Now with the help I learnt what it is in real life and might start using it myself - or at least would understand better when I see it. – Mykola Mar 28 '23 at 11:37
1

{ } means an object

The o is { } syntax reads o is an object and is an equivalent to o is not null.

And c is { Id: { } } reads c is an object and it's parameter Id is an object.

You can use SharpLab to see the generated code.

The lambda is written like this:

internal bool <M>b__0_0(Person c)
{
    return c != null && c.Id.HasValue;
}
Orace
  • 7,822
  • 30
  • 45
  • thanks for pointing to SharkLab! I didn't think of approaching this problem from this side! Yes, it is easy after somebody's explaination :) – Mykola Mar 28 '23 at 11:16