In this contrived C# 8 example:
#nullable enable
class Fred<T>
{
T Value; // If T is a nullable type, Value can be null.
public Fred() { }
public void SetValue(T value) { Value = value; }
public T GetValue() { return Value; }
public string Describe() { return Value.ToString() ?? "oops"; }
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George g = fredGeorge.GetValue();
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
I have three design goals:
So this first version isn't bad, I get a warning in Fred that Describe() might be dereferencing a null reference (satisfies goal #1) but I also get a warning that Value is uninitialized in Fred's constructor (violates goal #3) and George compiles without any warnings (violates goal #2). If I make this change:
public Fred() { Value = default; }
George still compiles without warnings (violates goal #2) and I get a different warning in Fred's constructor about a "Possible null reference assignment" (violates goal #3).
I can get rid of the possible null reference assignment by using the null-forgiving operator:
public Fred() { Value = default!; }
And now Fred only has the correct warning (possible dereference in Describe()), but George also compiles without warning (violates goal #2).
If I try to indicate that 'Value' can be null:
T? Value;
I get a compiler error that "A nullable type parameter must be known to be a value type or non-nullable reference type" so that's no good.
If I go back to
T Value;
and add the "MaybeNull" attribute:
[return: MaybeNull]
public T GetValue() { return Value; }
I get two warnings - one in Fred.Describe() warning of a possible null dereference (correct) and one in George warning that fredGeorge.GetValue() might be null (correct). There is no warning about fredFloat.GetValue() being null (correct).
So after adding code to expect null references, what I end up with is this:
class Fred<T>
{
T Value;
public Fred()
{
Value = default!;
}
public void SetValue(T value)
{
Value = value;
}
[return: MaybeNull]
public T GetValue()
{
return Value;
}
public string Describe()
{
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George? g = fredGeorge.GetValue();
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
Is this the correct pattern for this functionality?
C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...
In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.
What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.
C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.
In System.Diagnostics.CodeAnalysis
there is an attribute AllowNullAttribute
that specifies that null
is allowed as an input even if the corresponding type disallows it. I would use this attribute in your final sample to:
Value
. This will allow us to remove null-forgiving operator
in the assignment Value = default
. Compiler will not warn us about Possible null reference assignment
, because now it knows that null
value can be assigned to the property Value
.T value
of the method SetValue
. It will allow to pass null
value to the method SetValue
without getting compiler warning Cannot convert null literal to non-nullable reference type
. (Currently if we pass null
value to the method SetValue
we will get this warning)
Here is final sample with suggested changes:
class Fred<T>
{
// AllowNull attribute says that a null value
// can be assigned to the field Value.
[AllowNull]
private T Value;
public Fred()
{
// Now we can delete null-forgiving operator, because compiler knows
// that null value can be assigned to the field Value.
Value = default;
}
// AllowNull attribute says that a null value
// can be passed to the method SetValue.
public void SetValue([AllowNull] T value)
{
Value = value;
}
[return: MaybeNull]
public T GetValue()
{
return Value;
}
public string Describe()
{
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
George? g = fredGeorge.GetValue();
// Compiler does not warn us "Cannot convert null literal to
// non-nullable reference type" because it knows that a null
// value can be passed to the method SetValue.
fredGeorge.SetValue(null);
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.GetValue();
}
}
If we use a regular property instead of the field Value
with a pair of methods GetValue
and SetValue
then we can rewrite final sample in a clearer way:
class Fred<T>
{
// Here we tell that:
// 1) a null value can be assigned;
// 2) a null value can be returned.
[AllowNull, MaybeNull]
public T Value { get; set; }
public Fred()
{
// Compiler does not warn us "Possible null reference assignment".
// It knows that a null value can be assigned. It is correct.
// We can delete null-forgiving operator.
Value = default;
}
public string Describe()
{
// If we delete null checking, then we get a warning "Dereference of
// a possibly null reference". It is correct. Compiler helps us to avoid
// NullReferenceException.
return (Value == null) ? "null" : (Value.ToString() ?? "ToString is null");
}
}
class George
{
George()
{
Fred<George> fredGeorge = new Fred<George>();
// Compiler warns us "Converting null literal or possible null
// value to non-nullable type". It is correct.
// We should use nullable reference type George?.
George g = fredGeorge.Value;
// Compiler does not warn us "Cannot convert null literal to
// non-nullable reference type". It knows that a null value
// can be passed to the method SetValue. It is correct.
fredGeorge.Value = null;
Fred<float> fredFloat = new Fred<float>();
float f = fredFloat.Value;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With