C# 6.0 Null Propagation Operator & Property Assignment
Asked Answered
P

3

28

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!

Potbelly answered 11/9, 2015 at 8:42 Comment(5)
That's not how it works. The operator returns a value after checking for null.Lillia
There's nothing wrong with the operator - in fact many languages have similar operators. It simply doesn't do what you think it does, nor does its name suggest this. In fact, it would be an issue if it did act this way - the operator would behave differently depending on whether it was on the left or right of an assignment.Lillia
Why did you think that null propagation has to do with assignment? It should be more explicit than similar names in other languages (eg. null-safe as proposed for Java, safe navigation in Groovy, safe-call in Kotlin) that it doesn't assign anything but returns (propagates) nulls if applied on a null objectLillia
PS. what you posted doesn't do what you think it does. It doesn't check someValue for null, it checks a. There's no difference at all between a.SetValue and a?.SetValue regarding its parameters.Lillia
As workaround you could implement a generic extension method for setting arbitrary types of properties: public static void SetValue<T>(this T property, T value) { property = value; } And use it like art?.Prop1.SetValue("Hello");Tapster
E
28

You're not the only one! SLaks raised this as an issue (now here)

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.

Enrichetta answered 22/9, 2015 at 11:11 Comment(1)
Still an open request: github.com/dotnet/csharplang/discussions/6072Longboat
G
6

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;
}
Groundsill answered 11/9, 2015 at 9:3 Comment(2)
@series0ne the edit doesn't check the parameter someValue for null, it check the object a. The operator doesn't do what you think it does at all.Lillia
I've updated the entire question to illustrate my problem with this. It's not understanding how the operator works - I'm aware of that; it's understanding why, if C# 6.0 can transform null propagation into different forms (one being your suggested answer), why it cannot do this for property assignment, which it could absolutely do - there is just no compelling argument as to why it doesn't do this.Potbelly
E
-2

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

}

Exemplar answered 23/9, 2015 at 2:31 Comment(3)
person.Name = (person != null) ? "John Smith" : null; // in this statement if the condition evaluates to false, So "person = null" therefor "person.Name" does not exist.Exemplar
Person person = new Person() { Name = null }; //The object instance must exist. person.Name = (person.Name != null) ? "John" : null; Console.WriteLine(person.Name);Exemplar
This does not answer the question I am afraid; you are only illustrating ways of getting around null propagation, which every C# developer will have used prior to the null propagation operator introductionPotbelly

© 2022 - 2024 — McMap. All rights reserved.