0

I am working through a training class for ASP.NET Core 6 and in it, in a class declaration, it has:

public class Pie
{
    public Category Category { get; set; } = default!;
}

If I understand the default! correctly (I'm experienced with C# 3 so this is new to me) it is assigning a default value to this property when the object is created. And as this property is a class, it is assigning null. And the ! is telling the compiler that it's ok to assign a null to this non nullable property.

Is that correct? If so, this does not appear to be necessary as the code will compile without this assignment and with no assignment, that property is null. So why is this done?

Also, in my programming to date it was assumed that a property in an object, if nothing is assigned to it, is null. And that any class member that is not a simple type is by definition nullable. Is that no longer true?

gunr2171
  • 16,104
  • 25
  • 61
  • 88
David Thielen
  • 28,723
  • 34
  • 119
  • 193
  • I think that you are basically "breaking" the promise that `Category` will never be `null` by doing this. It would be a lot more readable to just use a nullable reference instead (`public Category? Category { get; set; }`) – UnholySheep Nov 10 '22 at 22:47
  • `= new Category()` may (may!) be a better option. But if it is, you may also want a `private set;` – Joel Coehoorn Nov 10 '22 at 22:51
  • this could also be helpful https://stackoverflow.com/questions/67505347/non-nullable-property-must-contain-a-non-null-value-when-exiting-constructor-co/69685202#69685202 – Vivek Nuna Nov 11 '22 at 02:27

3 Answers3

2

Is that correct?

Yes, your understanding is correct. Assuming Category is a reference type, default is the same as null here. If you assign null to a non-nullable reference type, you will get a warning (CS8625), so ! is used to silence it.

If so, this does not appear to be necessary as the code will compile without this assignment and with no assignment, that property is null. So why is this done?

Without the assignment, you will get another warning (CS8618). While your code still compiles, it is a very bad idea to just leave the property uninitialised. This is because later on when you access the property, you see that it's a non-nullable type, so you don't do any null-checks, but it could turn out that it can still be null because it hasn't been initialised.

Also, in my programming to date it was assumed that a property in an object, if nothing is assigned to it, is null. And that any class member that is not a simple type is by definition nullable. Is that no longer true?

Only reference types have the default value of null, and any reference type is nullable, both of which I think is true since C# 1. You can still assign null to your Category property, at your own risk. It's only a warning that will be emitted.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
2

Note, this goes off the assumption that Category is a class, which seeing you tagged this "nullable reference types", that's a pretty good assumption.

Whether you include = default! or not has no effect on the final compiled code.

public class C {
   public string A { get; set; }
   public string B { get; set; } = default!;
}

A and B will be treated the same way by the compiler.

public class C
{
    [CompilerGenerated]
    private string <A>k__BackingField;

    [CompilerGenerated]
    private string <B>k__BackingField;

    public string A
    {
        [CompilerGenerated]
        get { return <A>k__BackingField; }
        [CompilerGenerated]
        set { <A>k__BackingField = value; }
    }

    public string B
    {
        [CompilerGenerated]
        get { return <B>k__BackingField; }
        [CompilerGenerated]
        set { <B>k__BackingField = value; }
    }
}

And this makes sense. default of a class is null, so = default just makes the property (and backing field) null, which is what would have been done anyways had you not typed = default.

So why the !? It's to make the compiler not throw a warning.

No really, that's it. It's to suppress the compiler warning you would have got had it not been there. When you use nullable reference types, the compiler wants you to always assign a value to your properties before the constructor exits. Using ! calms the compiler down as you pinky-promise that it won't be null when you try to read it back, but satisfies the requirement that some value is assigned.

public string A { get; set; }
// "A" might have a null value after the constructor exits

public string A { get; set; } = default;
// "Uh dude, you're trying to assign null to something that doesn't accept nulls".

public string A { get; set; } = default!;
// "Ok, you're assigning null, but I'm going to trust you know what you're doing".

You can also get around the problem by specifying a value in the constructor

public class C {
   public string A { get; set; } // no warning

   public C()
   {
       A = "f";
   }
}

Another thing to note, C# 11 just came out (November 2022), which has the required keyword, which takes care of all this nonsense.

public class C
{
    public required string A { get; set; }
    // no warning, no default, no constructor needed
}
gunr2171
  • 16,104
  • 25
  • 61
  • 88
0

Not initializing is bad practise. using default on a non nullable type gives the CS8625 warning, in my view that warning basically says that the compiler is ignoring that option (assigning a null value will lead to disapppointments, try it you have doubts, but the compiler wont even try), the ! is to prevent the warning (and to hide the warning so you don't get difficult questions on a review..). It's quite a lot of work to get nothing...i think not using anything has the same effect, maybe the compiler is really smart and fixes it and makes it an empty string... if u want to be sure just use:

public string A { get; set; } = string.Empty;

public List<string> A { get; set; } = new List<string>(); 

and for most nullables you probably 75% of the time also want to do the same (or create a new object if its an object)

public string? A { get; set; } = string.Empty;


public List<string>? A { get; set; } = new List<string>();

in code you probably use logic like

if (string.IsNullOrEmpty(A))
{
    DoSomething();
}
else
{
    DoSomethingDifferent();
}

when database administrators need to add a field to the database its usually a nullable, so existing programs keep working, rarely in my field of work there is a difference between an empty string or a null in terms of business logic.

JdB
  • 11
  • 2