C# 6.0 Null Propagation Operator & Property Assign

2019-01-19 10:23发布

问题:

This question has been completely overhauled in the interest of being thorough in explanation.

I have noticed what appears to be quite a poor limitation of the null propagation operator in C# 6.0 in that you cannot call property setters against an object that has been null propagated (though you can call property getters against an object that has been null propagated). As you will see from the generated IL (which I have reflected into C#), there is nothing that should limit the ability to call property setters using null propagation.

To start with, I have created a simple class, with both Java style Get/Set methods, and a property with public getter/setter access.

public class Person
{
    public Person(string name, DateTime birthday)
    {
        Name = name;
    }

    public string Name { get; set; }

    public void SetName(string name)
    {
        Name = name;
    }

    public string GetName()
    {
        return Name;
    }
}

I have tested the ability of null propagation in the following test class.

public class Program
{
    public static void Main(string[] args)
    {
        Person person = new Person("Joe Bloggs", DateTime.Parse("01/01/1991"));

        // This line doesn't work - see documented error below
        person?.Name = "John Smith";

        person?.SetName("John Smith");

        string name = person?.Name;
    }
}

The left-hand side of an assignment must be a variable, property or indexer.

You may notice from this however that the Java way of setting the name, by calling SetName(...) works, and you may also notice getting the value of a null propagated property also works.

Let's take a look at the C# that was generated from this code:

public static void Main(string[] args)
{
    Person person = new Person("Joe Bloggs", DateTime.Parse("01/01/1991"));
    if (person != null)
    {
        person.SetName("John Smith");
    }
    string arg_33_0 = (person != null) ? person.Name : null;
}

Notice that when used against the SetName method, null propagation transforms to a straightforward if statement, and that when used against the Name property getter, a ternary operator is used to either get the value of Name or null.

One thing I have noticed here is the behavior difference between using an if statement and using the ternary operator: when using a setter, using an if statement would work, whereas using a ternary operator wouldn't.

public static void Main(string[] args)
{
    Person person = null;

    if (person != null)
    {
        person.Name = "John Smith";
    }

    person.Name = (person != null) ? "John Smith" : null;
}

In this example I am using both an if statement and the ternary operator to check whether person is null before attempting to assign to its Name property. the if statement works as expected; the statement using the ternary operator fails, as expected

Object reference not set to an instance of an object.

In my opinion, the limitation comes from C# 6.0's ability to transform null propagation into either an if statement or a ternary expression. Had it been designed to use only if statements, property assignment would work via null propagation.

So far, I have not seen one compelling argument as to why this SHOULD NOT be possible, therefore I am still looking for answers!

回答1:

You're not the only one! SLaks raised this as an issue

Why can't I write code like this?

Process.GetProcessById(2)?.Exited += delegate { };

and after it was briefly closed as "By design"

the ?. Operator never produces an lvalue, so this is by design.

someone commented that it would be good for property setters as well as event handlers

Maybe add also properties setters into request like:

Object?.Prop = false;

and it was re-opened as a feature request for C#7.



回答2:

You can't use the null-propagation operator in this way.

This operator allows to propagate nulls while evaluating an expression. It can't be used as the target of an assignment exactly as the error suggests.

You need to stick to the plain old null check:

if (a != null)
{
    a.Value = someValue;
}


回答3:

Try it like this...

using System;

namespace TestCon
{
    class Program
    {
        public static void Main()
    {

        Person person = null;
        //Person person = new Person() { Name = "Jack" };

        //Using an "if" null check.
        if (person != null)
        {
            Console.WriteLine(person.Name);
            person.Name = "Jane";
            Console.WriteLine(person.Name);
        }

        //using a ternary null check.
        string arg = (person != null) ? person.Name = "John" : arg = null;
        //Remember the first statment after the "?" is what happens when true. False after the ":". (Just saying "john" is not enough)
        //Console.WriteLine(person.Name);

        if (arg == null)
        {
            Console.WriteLine("arg is null");
        }

        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
public class Person
{
    public string Name { get; set; }
}

}