1

I'm using C# 8 with nullable enabled and now I'm having a problem with my regex loop:

public static async Task<IEnumerable<WorkerDto>?> GetOwnersAsync(LampContext context, string? ownerString)
{
    if (string.IsNullOrWhiteSpace(ownerString))
        return null;

    var wwids = new List<int>();

    var matches = Regex.Matches(ownerString, @"\d+");
    foreach (Match match in matches)
        wwids.Add(int.Parse(match.Value));

It's saying I have a possible null reference assignment to the match iteration variable, and I'm not sure why it would say that, or how to get around it. The documentation for Matches says it'll return an empty collection, not null.

How am I supposed to write that code now?

Gargoyle
  • 9,590
  • 16
  • 80
  • 145
  • could you please post the initialization for `ownerString` & `wwids` – Andrei Nov 14 '19 at 19:50
  • Updated the question to show ownerString is nullable. Even if I replace `ownerString` with `""` in the `Regex.Matches` I'll still get the error though. – Gargoyle Nov 14 '19 at 19:52

1 Answers1

4

The problem is that MatchCollection implements the non-generic IList interface in preference to the generic IList<Match> interface, for backwards compatibility, and IEnumerator.Current is defined as object?, so the foreach actually casts this nullable object to a non-nullable Match. It's as if you had written

foreach (object o? in matches) {
    Match match = (Match) o;
    ...
}

All this comes from a long forgotten time before we had C# 2, and now comes back to bite us in C# 8.

There are several effective workarounds; one of the simplest is to assign matches to a variable of type IList<Match> or IEnumerable<Match> (as it does implement the generic interface):

IList<Match> matches = Regex.Matches(ownerString, @"\d+");
foreach (Match match in matches)
    wwids.Add(int.Parse(match.Value));

Note that we do not need a cast here. Another is to use .AsEnumerable() to effectively cast it:

var matches = Regex.Matches(ownerString, @"\d+");
foreach (Match match in matches.AsEnumerable())
    wwids.Add(int.Parse(match.Value));

And last but not least, if all you're doing is .Adding these values to a new list, you may as well construct the list in one go with LINQ:

var wwids = matches.Select(m => int.Parse(m.Value)).ToList();
Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85
  • Great answer! Would `foreach (var match in matches.OfType())` work as well? Because that's what I usually do but I haven't had the chance to use C# 8 yet. – 41686d6564 stands w. Palestine Nov 14 '19 at 21:12
  • @AhmedAbdelhameed: Sure, that works, but `.OfType<>` is actually a filter -- it returns all elements of that type, and leaves out others. Of course in this case they always match, but if you know this, a more effective expression of that is to use `.Cast<>`, which will throw if there's a mismatch. I would use neither in this case because `MatchCollection` actually does implement `IEnumerable` -- it doesn't strictly need casting, unlike collections that only implement `IEnumerable`. – Jeroen Mostert Nov 14 '19 at 21:15