Hammerite
Hammerite

Reputation: 22350

Is there an opposite to the null-forgiving operator?

This program generates compiler warning CS8620

Argument of type 'IEnumerable<(string S1, string S2)>' cannot be used for parameter 'e' of type 'IEnumerable<(string? s1, string? s2)>' in 'void Program.F(IEnumerable<(string? s1, string? s2)> e)' due to differences in the nullability of reference types.

But I'm not trying to pass off something that can be null as something that can't, I'm doing the opposite! That should be perfectly fine!

Is there some way I can tell the compiler, "look I know it can't be null, just pretend that it can be"? An opposite to the null-forgiving operator? A null-condemning operator?!

namespace MyNamespace;

public static class Program
{
    public static void Main(string[] args)
    {
        var L = new [] {new C("S1", "S2")};
        F(L);
    }

    public class C
    {
        public string S1 { get; }
        public string S2 { get; }

        public C(string s1, string s2)
        {
            S1 = s1;
            S2 = s2;
        }
    }

    public static void F(IEnumerable<C> e)
        => F(e.Select(c => (c.S1, c.S2)));

    public static void F(IEnumerable<(string? s1, string? s2)> e)
    {}
}

Upvotes: 1

Views: 205

Answers (4)

Guru Stron
Guru Stron

Reputation: 143098

If the goal is to remove the warning than null-forgiving operator should work:

public static void F1(IEnumerable<C> e) => F(e.Select(c => (c.S1, c.S2))!);

Or specifying nullable string? for both tuple elements:

public static void F2(IEnumerable<C> e) 
    => F(e.Select(c => ((string?)c.S1, (string?)c.S2)));

Explicitly typed Select works too, but it requires specifying both source and target types:

public static void F3(IEnumerable<C> e)
   => F(e.Select<C, (string? s1, string? s2)>(c => (c.S1, c.S2)));

Demo @sharplab

Upvotes: 0

user555045
user555045

Reputation: 64913

But I'm not trying to pass off something that can be null as something that can't, I'm doing the opposite! That should be perfectly fine!

It would be fine if the original collection cannot be modified through the IEnumerable .. but it can be. Consider this code, which yes, is a bit of a hack, that's the point:

public static void Main(string[] args)
{
    var L = new[] { ("S1", "S2") };
    F(L);
    Console.WriteLine(String.Join(", ", L));
}

public static void F(IEnumerable<(string? s1, string? s2)> e)
{
    if (e is (string? s1, string? s2)[] array)
        array[0].s1 = null;
}

L is an array of tuples of regular strings (not string?). The body of the if in F is executed, and a null ends up in that tuple anyway. So the compiler warning about that call to F is not wrong.

Upvotes: 1

InBetween
InBetween

Reputation: 32780

You can do this:

#nullable disable
public static void F(
    IEnumerable<C> e) => F(e.Select(c => (c.S1, c.S2)));
#nullable enable

Upvotes: 0

David
David

Reputation: 219037

The three ways I can think of are:

Either make the properties string?:

public string? S1 { get; }
public string? S2 { get; }

Or make the type arguments string:

public static void F(IEnumerable<(string s1, string s2)> e)

Or, if you can't change either, use the ?. operator when referencing the properties:

=> F(e.Select(c => (c?.S1, c?.S2)));

Upvotes: 0

Related Questions