Why does C# not warning about non-nullable long field? - c#

Why do I not get a non-nullable warning (or some other warning) for FieldTwo not being set in the constructor? I'm using .Net 5.0 and Nullable option is set to true in the project file.
public class MyClass
{
public string FieldOne;
public long FieldTwo;
public MyClass (string p1, long p2)
{
FieldOne = p1;
// FieldTwo is not set. Where is the non-nullable warning!?
}
}

long is a value type and cannot be null. So there is no reason for warn about null. What I suspect you want is a field not initialized warning.
If a private field is never assigned, it causes a CS0649 warning. However, if the field public, or is modified anywhere in the class, this warning is not raised. For example, the following would be perfectly acceptable code, and should not give a warning:
public class MyClass{
private long myLong;
public void Increment() => myLong++;
}
If the field should only be set from the constructor you should declare it as readonly. However, public readonly fields does not trigger a warning, even if they probably should. See Why do I NOT get warnings about uninitialized readonly fields?.

long is a value type and can't be null. Value types are always initialized when declared with a default value, in this case 0. You'd have to change the field type to long? if you wanted to store a null
Those fields aren't properties anyway. This isn't just semantics. Fields are implementation details, even public fields. They aren't considered part of a class's API surface. Properties are part of the API, they get serialized by default by all serializers (fields don't) and guarantee In fact having public fields is a code smell.
To ensure all properties are initialized you can use a record type instead of a class :
public record(string PropertyOne, long PropertyTwo);
This generates a class with init-only properties for PropertyOne and PropertyTwo and a constructor that requires values for all properties. Along with equality operators, deconstructors and a ToString() implementation that returns all properties in the form of a JSON string.

Related

How can C# constructor assign value to read only property?

I wrote a class property with only get accessor, like:
public int MyProp { get; }
I cannot assign any value to MyProp, not even privately, via method inside my class, i.e. it's not like if I had a private set.
However, somehow, I can set a value to myProp using a constructor like:
public MyClass (int myProp) { this.MyProp = myProp; }
Is the constructor always immune to property accessor specifications? Are they valid only after the constructor runs?
When you create an auto-property the compiler automatically generates backing fields to support it. In the case of a readonly (get only) property this is still the case. Being able to assign to a get-only property in the constructor is simply syntactic sugar supported by the compiler. When you assign to the property, the compiler translates the code so that it assigns to the backing field instead.
For example, assume your constructor:
public MyClass(int myProp)
{
this.MyProp = myProp;
}
This is translated by the compiler to something akin to the following:
public MyClass(int myProp)
{
this._myPropBackingField = myProp;
}
The fact that you have no set accessor then prevents you from assigning to the property everywhere else.
In reality, the backing field recieves an "unspeakable" name that is illegal c# (but valid IL) thus preventing you from attempting to use the field directly. Your example actually looks like the following after the compiler has done its job:
public class MyClass
{
[CompilerGenerated]
private readonly int <MyProp>k__BackingField;
public int MyProp
{
[CompilerGenerated]
get
{
return this.<MyProp>k__BackingField;
}
}
public MyClass(int myProp)
{
this.<MyProp>k__BackingField = myProp;
}
}
Note that the constructor is actually assigning to the backing field <MyProp>k__BackingField directly rather than indirectly via the property's setter (since after all one does not exist).
See this SharpLab example.
Just think if the constructor is not allowed to initialize these values then what is the use of these values? It will always be the datatype default value like for int it will be 0. By using only get means we will not allow to change the value once it's initialized.
You can use readonly also, and it is thread-safe by default. but when you use a private set you have to handle the thread-safety explicitly.

Avoid CS8618 warning when initializing mutable non nullable property with argument validation

I have a question regarding nullable reference type system available since C# 8.
Suppose we have a C# domain model class with a mutable reference type property like below:
public class Person
{
public string Name { get; set; }
public Person(string name)
{
Name = name;
}
}
So far no problem. But consider real world scenario, I often want to check the validity of the property as it's a public mutable property and I have to make sure the model invariant whenever property is changed.
public class Person
{
private string _name;
public string Name
{
get => _name;
set => _name =
value ?? throw new ArgumentNullException("Name is required.");
}
public Person(string name)
{
Name = name;
}
}
Then compiler generates CS8618 warning, basically saying:
Non nullable field _name is not initialized. Consider declare the
field as nullable type.
So every time I encounter the warning I have to enclose the constructor with the following pragma directive.
#pragma warning disable CS8618
public Person(string name)
{
Name = name;
}
#pragma warning restore CS8618
But I think it's redundant and tedious to do it always. Am I misusing something or is there better way to write such property without warning?
Of course I can change the property type to string? as compiler suggests but notionally it's not acceptable as a solution as Person should always have non null name and we want to explicit about such invariant condition in domain class.
Another solution I considered is to drop the argument validation logic and just relying on the nullable compiler warning, but it's not always possible (I mean often validation other than null check is also required.), it's just warning anyway in regular project settings, so I don't think it's a good solution.
For now you can avoid this warning by initializing a _name field using default value with null-forgiving operator !, like
private string _name = default!;
or
private string _name = null!;
There is also an open GitHub issue for that.
You can also declare the _name as string? and specify that return value of Name property can't be null (even if string? type allows it), using NotNull attribute
private string? _name;
[NotNull]
public string? Name
{
get => _name;
set => _name = value ?? throw new ArgumentNullException("Name is required.");
}
It should be fine, otherwise compiler shows you a warning before validation logic will take place in a setter
set => _name = value ?? throw new ArgumentNullException("Name is required.");
Consider the following code
var person = new Person(null);
In this case you'll get
warning CS8625: Cannot convert null literal to non-nullable reference
type.
before ArgumentNullException will be thrown.
If you set <TreatWarningsAsErrors>true</TreatWarningsAsErrors> or treat CS8625 warning as error, your exception won't be thrown
You can disable the rule by creating an .editorconfig file (with the attached code) in the root of your project. It does not solve it but it will no longer show the warning
[*.cs]
# CS8618: Non nullable field _name is not initialized. Consider declare the field as nullable type
dotnet_diagnostic.CS8618.severity = none
You can now apply MemberNotNull attribute on the setter to let the C# compiler know that non-nullability condition for the _name field is being maintained by that method.
C# language reference
using System.Diagnostics.CodeAnalysis;
public class Person
{
private string _name;
public string Name
{
get => _name;
[MemberNotNull(nameof(_name))]
set => _name = value ?? throw new ArgumentNullException("Name is required.");
}
public Person(string name)
{
Name = name;
}
}
Based on this:
Warnings for initialized fields
Q: Why are warnings reported for fields that are initialized indirectly by the constructor, or outside the constructor?
A: The compiler recognizes fields assigned explicitly in the current
constructor only, and warns for other fields declared as non-nullable.
That ignores other ways fields may be initialized such as factory
methods, helper methods, property setters, and object initializers. We
will investigate recognizing common initialization patterns to avoid
unnecessary warnings.
So with that being said, for now, moving the assignment directly into the constructor, is the only possible way. And for sure, using the pragma directive seems fine for this IMO.
I'd like to further expand Pavel Anikhouski's answer by pointing out that using null-forgiving operator is considered a proper practice according to MS C# language reference.
You can also use the null-forgiving operator when you definitely know that an expression cannot be null but the compiler doesn't manage to recognize that.
To disable this warning and other null-related warnings for the whole Project, right click on your project -> Properties. Under the section Build -> General, set Nullable to Disable.
Tested on Visual Studio version 17

How to use C# 8.0 Nullable Reference Types with Entity Framework Core models?

I am enabling C# 8.0 Nullable Reference Types on a .NET Core 3.0 project. The project uses Entity Framework Core 3.0 to access database.
The following is a data model whose Title should not be null.
public class Vehicle
{
public int Id { get; private set; }
public string Title { get; private set; }
// Entity Framework Core is instructed to bind to the private _drivers field in a configuration builder
private readonly List<Driver> _drivers = new List<Driver>();
public IReadOnlyCollection<Driver> Drivers => _drivers.AsReadOnly();
private Vehicle()
{
}
public Vehicle(string title)
{
this.Title = title;
}
public void AddDriver(string name)
{
this._drivers.Add(new Driver(name));
}
}
// A foreign column is defined in a configuration builder
public class Driver
{
public int Id { get; private set; }
public string Name { get; private set; }
private Driver()
{
}
public Driver(string name)
{
this.Name = name;
}
}
Own code is supposed to use the public constructors only while the private constructors are there just to allow Entity Framework Core and (potentially also) serialization to bind values from database to these classes/models. The public constructor might have different structure, list and types of arguments than which properties the model has (for example, it might also contains arguments for the first required child, it might have some arguments optional etc.).
However, the compiler generates CS8618 Non-nullable field is uninitialized. Consider declaring as nullable. on the private constructors.
I am able to disable CS8616 for the private constructors by #pragma warning disable CS8618 but I do not consider that as a good idea.
How it is supposed to use C# 8.0 Nullable Reference Types in this scenario?
Or is my model bogus or violates best practices - how to do it properly?
Unfortunatelly, I have found not relevant docs or guidance.
There is no proper way to handle non-nullable navigational properties.
Documentation suggests two ways and both are not type safe. Use a
backing field and throw InvalidOperationException. It is unclear how
it differs from doing nothing and have a NullReferenceException
Suppress it with null forgiving operator
Official documentation link: https://learn.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types#non-nullable-properties-and-initialization
I agree with you- pragma blocks are ugly. Instead, I would assign the null forgiving operator to the non-nullable reference type inside the default constructor, like so:
private Vehicle()
{
Title = null!;
}
This is much cleaner and more expressive than initializing the property with said operator inline like so:
public string Title { get; private set; } = null!;
The latter solution reads as "I know Title is not null regardless of scenario" which effectively negates the benefit of non-nullable reference types since you lose all of your design time checks. The former reads as "I know Title is not null in this specific scenario", so compiler warnings continue to be issued elsewhere if you miss an assignment.
From the MS Docs for Entity types with constructors
When EF Core creates instances of these types, such as for the results
of a query, it will first call the default parameterless constructor
and then set each property to the value from the database. However, if
EF Core finds a parameterized constructor with parameter names and
types that match those of mapped properties, then it will instead call
the parameterized constructor with values for those properties and
will not set each property explicitly.
Perhaps it is worth creating a private ctor with the parameter needed for those properties and see if the Framework will then call that and work?
Also disabling warnings is not a good idea unless you are fully 100% confident that it is ok to disable it.

Property Initializer cannot reference non-static field

I thought the new C# 6.0 property initializers like this.
public MyType MyProperty { get; } = new MyType(OtherProperty);
was the equivalent of this
private MyType _myVariable;
public MyType MyProperty { get { return _myVariable ?? _myVariable = new MyType(OtherProperty); } }
(that OtherProperty is available as part of the instance, not limited to being static)
But in the above first I get "field initializers cannot reference non-static field". Am I doing it wrong, or are property initializers just as limited as
public readonly MyType MyVariable = new MyType(NeedsStaticReference);
In your second example the field is set on first use.
The problem here, is the field intializer is set immediately before the constructor, and there is no guarantee the other property is set or constructed or what order this happens.
If you want to assign something on construction you will need to do it in the constructor
Fields (C# Programming Guide)
Fields are initialized immediately before the constructor for the
object instance is called. If the constructor assigns the value of a
field, it will overwrite any value given during field declaration.
A field initializer cannot refer to other instance fields.
And some more information
A field can optionally be declared static. This makes the field
available to callers at any time, even if no instance of the class
exists. For more information, see Static Classes and Static Class
Members.
A field can be declared readonly. A read-only field can only be
assigned a value during initialization or in a constructor. A
static``readonly field is very similar to a constant, except that the
C# compiler does not have access to the value of a static read-only
field at compile time, only at run tim
It's, actually, like this:
private readonly MyType _myVariable = new MyType(OtherProperty);
public MyType MyProperty { get { return _myVariable; } }
Thus, the issue.

Default and custom constructor

I have a simple question.
Assume that I have class like below.
public class DamageToDeal
{
public bool enabled;
public float value;
public TDValue type;
public DamageToDeal() { }
public DamageToDeal(bool _enabled, float _value, TDValue _type)
{
enabled = _enabled;
value = _value;
type = _type;
}
}
I read that if I have custom constructor the default is not automaticaly generetared
Do I have to initialize fields myself with default values(0, null) or default constructor with empty body will do it anyway?
Or if the default constructor is initializing fields even if he has empty body ?
Memory allocated to a new class instance is cleared by the memory allocator. You only have to ensure that any fields you want to have a non-default value is assigned.
This is documented here: Fundamentals of Garbage Collection:
Managed objects automatically get clean content to start with, so their constructors do not have to initialize every data field.
You do not need an empty parameterless constructor for this to happen. You would only add that constructor if you actually want to call it and that makes sense for the type.
Also note that any field declarations that also states an initialization expression is lifted into constructors.
If you do this:
public TDValue type = new TDValue();
then regardless of which constructor is called that field will have an instance reference to a new TDValue object.
Note that the above is valid for classes, not for structs. For structs you need to ensure you assign all fields because memory is not always "allocated", it might be just reserved on the stack.

Categories