17

I have switched to enable nullable in my project that uses C#8. Now I have the following class:

public class Request
{
    public string Type { get; set; }
    public string Username { get; set; }
    public string Key { get; set; }
}

Compiler of course complains that it cannot guarantee that these properties won't be null. I can't see any other way of ensuring this than adding a constructor that accepts non-nullable strings.

This seems fine for a small class, but if I have 20 properties, is this the only way to list them all in a constructor? Is it possible somehow to e.g. enforce it with the initializer:

var request = new Request { Type = "Not null", Username = "Not null" }; // Get an error here that Key is null

P.S. There is a good answer that suggests using iniatlizer for properties, but that does not always work e.g. for types like e.g. Type that one cannot just intialize to some random value

Ilya Chernomordik
  • 27,817
  • 27
  • 121
  • 207
  • 3
    The initializer creates an *empty* object, then sets the values. It's not the same as a constructor accepting parameters. The code isn't *less* either. Do you really want to use an object initializer? – Panagiotis Kanavos Dec 30 '19 at 12:43
  • @PrasadTelkikar I don't think what you say is true. I get a compiler warning at least that I have to initialize it myself. Or even if it is true, it does not prevent the warning – Ilya Chernomordik Dec 30 '19 at 12:44
  • 1
    If you do, you can set the default value in the property itself. You can add attributes that tell the compiler the properties won't be null once set, eg `NotNull` – Panagiotis Kanavos Dec 30 '19 at 12:44
  • 1
    @PanagiotisKanavos No, it was just a theoretical possibility I thought of, I am just really trying to understand a correct course of action with the new non-nullable types. I really don't want to accept null in there since I know it's not valid beforehand, but writing constructors with a lot of arguments seems a bit too much – Ilya Chernomordik Dec 30 '19 at 12:45
  • @IlyaChernomordik the options *are* there but you have to decide what you actually want. Are those "optional" properties with defaults? The props or fields *have* to be initialized, and `null` may or may not be an appropriate default now. – Panagiotis Kanavos Dec 30 '19 at 12:46
  • Initializer had an advantage of being explicit, while using constructor is optional to specify the names of the parameters, making code a bit more implicit and prone to errors if e.g. order is changed one place only. But then again I was just trying to review my options in here – Ilya Chernomordik Dec 30 '19 at 12:48
  • I can say the same thing with the names reversed. The initializer isn't more explicit, it creates an object with default property values that need changing after the fact. It prevents immutability. With the proper attributes you can ensure non-null values once set. – Panagiotis Kanavos Dec 30 '19 at 12:50
  • Your question is important for specific types of objects, eg configuration objects, that *can't* be read-only due to API restrictions. Even then, we have to decide what we want as a default value if we don't want null – Panagiotis Kanavos Dec 30 '19 at 12:52

2 Answers2

7

Object initializer syntax is really just short-hand for explicitly assigning to fields or property setters, i.e.

var request = new Request { Type = "Not null", Username = "Not null" };

Is equivalent to:

var request = new Request();   // <-- All properties are default
request.Type = "Not null";     // <-- Username and key are default
request.Username = "Not null"; // <-- Key is still default

As you can see, the Request instance still goes through several states where the properties are in a default status, which will be null for reference types like string unless you assign a different default value in the constructor, as per the other answers.

Also, by specifying different default values

public string Type { get; set; } = ""
public string Username { get; set; } = "";
public string Key { get; set; } = "";

is equivalent to assigning these values in a default constructor, i.e.

public Request()
{
    Type = "";
    UserName = "";
    Key = "";
}

As you can imagine, this is a bit wasteful if you then immediately change the value again using object initializer syntax.

As an alternative, and if you don't require a mutable class, I would suggest instead that you provide one or more constructor overloads, which then supply suitable non-null default values for any missing fields, and then make the properties immutable e.g.

public class Request
{
    public Request(string type = "", string userName = "", string key = "")
    {
         Type = type;
         Username = userName;
         Key = key;
    }

    public string Type { get; }     // <-- No setter = immutable.
    public string Username { get; }
    public string Key { get; }
}

You can now instantiate the class without ever going through a state where any of the properties are ever null, and without the overhead of the intermediate default assignment, e.g.

var myRequest = new Request(key: "SomeKey"); // <-- Username and Type are defaulted to ""
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • 1
    C#9 has a new "init" keyword, but it does not help yet. There is a github issue here (https://github.com/dotnet/roslyn/issues/44889) that might address it, so that we can in the future use initializer syntax – Ilya Chernomordik Nov 27 '20 at 13:07
5

Initialise them in the definition

public string Type { get; set; } = ""
public string Username { get; set; } = "";
public string Key { get; set; } = "";

The object initializer syntax "new Request { }" is just syntactic sugar for new then assignment, so don't think you can get that to error in this case

Milney
  • 6,253
  • 2
  • 19
  • 33
  • Is there are 2 ways only then? Constructor or direct initialization? P.S. What about types with protected constructor, like `Type` e.g., then it's only constructor? – Ilya Chernomordik Dec 30 '19 at 13:23