Warning CS0219 on unused local variable depends on Nullable<> syntax? - c#

Consider this code:
static void Main()
{
int? a = new int?(12); // warning CS0219: The variable 'a' is assigned but its value is never used
int? b = 12; // warning CS0219: The variable 'b' is assigned but its value is never used
int? c = (int?)12; // (no warning for 'c'?)
}
The three variables a, b and c are really equivalent. In the first one, we call the public instance constructor on Nullable<> explicitly. In the second case, we utilize the implicit conversion from T to T?. And in the third case we write that conversion explicitly.
My question is, why will the Visual C# 5.0 compiler (from VS2013) not emit a warning for c the same way it does for the first two variables?
The IL code produced is the same in all three cases, both with Debug (no optimizations) and with Release (optimizations).
Not sure if this warning is covered by the Language Specification. Otherwise, it is "valid" for the C# compiler to be inconsistent like this, but I wanted to know what the reason is.
PS! If one prefers the var keyword a lot, it is actually plausible to write var c = (int?)12; where the cast syntax is needed to make var work as intended.
PPS! I am aware that no warning is raised in cases like int? neverUsed = MethodCallThatMightHaveSideEffects();, see another thread.

Related

Wondering why to use `string?` instead of `string` in a property declaration

I have always developed using ASP.NET Framework, where I used a string property this way: public string FirstName { get; set; }.
Now I started a new .NET Core 6 project and I declared the same property in a custom IdentityUser class.
In this case, Visual Studio told me that it is better to use nullable string. Why does it suggest that since a string type can be already null?
Suggestion message appears in Spanish but it is basically what I have described.
That suggestion is gone when I use string?. Note that if I use Nullable<string> shows a compiler error since string is a reference type.
Just wondering.
Thanks
Jaime
No need to use Nullable<> type. That's for value types. Just decide if you want to use the Nullable Context. You will find this setting under
Project Properties >> Build >> General >> Nullable.
You have two basic options here.
Turn off the setting to make your code work like before (I would not do this)
Make your code honor the setting.
When this Nullable Context enabled, you are telling the compiler the following:
Any reference type declared without the ? symbol may never be null. It must always refer to a valid object.
Likewise any reference type declared with the ? symbol right after the type means that it may be null, just like old-school C# code.
That goes for strings, or any other reference type. The code-analyzer to checks for this and complains. So when you declare your property like this...
public string FirstName { get; set; }
Then you need to ensure in your constructor that this property is initialized to some valid string object or the code-analyzer will complain. Or you could declare it with a default value like this:
public string FirstName { get; set; } = string.Empty
If you want to be able to set the FirstName to null -- if that actually makes sense for your project -- then declare it with the question mark
public string? FirstName { get; set; }
Now it acts like an old style reference type, before C# 8.0.
Once you turn this setting on you'll find yourself dealing with this a lot. If you have warnings set to the highest level you'll be forced to chase down all these things in your code and address them. This is a good thing. Don't avoid it. Yes it is a pain to address up front but it saves you countless headaches down the road.
I once did it for an established application and spent two full days fixing all the warnings it generated. But in the year or two since then, I have lost count of the number of times this feature made the compiler/code-analyzer catch me failing to initialize a non-nullable reference type and saved me from a potential NullReferenceException down the line. I think this feature is priceless.
Nullable Context forces you to think about every reference type you have when you write the API. Can that reference be null or not? It makes you set the rule and honor it and is a lifesaver in a large project with multiple developers
Note: If you do use Nullable Context, you will want to be familiar with a very useful code attribute to use on some out values for functions: The [MayBeNullWhen] attribute. It comes in handy when you write a Try-type function to retrieve a reference type.
For example, suppose you wrote a function like this. This would work fine before but generates errors with Nullable Context enabled
public bool TryGetWidget(out Widget value)
{
value = null; // *** ERROR: Not valid if value is not nullable
if (/* some code that tries to retrieve the widget */)
value = retrievedWidget;
return value != null
}
The implication here is that the function might not succeed and return false. But if Widget is a reference type, then it will have to set the out value to be null in this case. Nullable Context will not allow that. You declared the out value as out Widget not out Widget? So you cannot set it to null. So what do you do?
You could change the argument to out Widget?
public bool TryGetWidget(out Widget? value)
That would make the function build. But then the Nullable context would complain if you tried to use the return value after the function returned true.
if (TryGetWidget(out var widget))
widget.SomeFunction(); // ERROR: `widget` might be null
Seems you can't win either way, doesn't it? Unless you use [MayBeNullWhen(false)] on the function declaration
public bool TryGetWidget([MaybeNullWhen(false)] out Widget value)
Now, your function will compile and your code that calls it will compile. The compiler/code-analyzer is smart enough to realize that a true return means you can use the out reference and a false returns means you cannot.
In addition to the ? operator there is also the ! operator. This one tells the compiler, "Ignore nullability. Assume this reference is valid" So if you had a function like this
Widget? GetWidget();
The fillowing code would not compile
GetWidget().SomeFunction(); // ERROR: Return value of GetWidget() Might be null
but this would compile by forcing the compiler to pretend the return value is valid.
GetWidget()!.SomeFunction(); // Better hope that returned reference is valid.

Why are interface methods returning non-nullable reference types being treated as nullable vars? [duplicate]

This question already has answers here:
Why does Visual Studio Type a Newly Minted Array as Nullable?
(3 answers)
Closed 6 months ago.
I'm new to C#10 and the default enabled nullable NRT functionality. In the following simple example I'm curious why VS2022 tells me (via tooltip) that o is object? when the interface definition explicitly states it is object.
Is this standard behavior and what's the reason for it? Is it because the compiler has no way of telling that the interface implementation is null-safe so has to assume the worst?
Further, s is also nullable (string?) but no warnings or errors are given when I use these nullable types. The compiler confidently informs me that "o is not null here" when I call o.ToString.
I can't tell at this stage if NRTs marks a paradigm shift to how we write modern C# or is "just another language tweak?" What's going on here? I want to 'embrace' it but am struggling with the full understanding of another change to the language.
public interface ITest
{
Object GetObject();
}
public class Tester
{
public void Test(ITest i)
{
var o = i.GetObject(); //object?
var s = o.ToString(); //string?
Console.WriteLine(s);
}
}
Although an interface implementation written in C# might not be able to return null, there is no requirement that interface implementations be written only in C#. If an interface is implemented by code written in another language, nothing would prevent that code from returning null.
A similar issue arises, incidentally, with the TryGetValue method of IDictionary<TKey,TValue>. If one has a structure with member m whose constructor invokes:
SomeIDictionary.TryGetValue(someKey, ref m);
the C# compiler will blindly assume that the interface method will not return without storing a value into m (e.g. because the key is not found). This may have the surprising effect of causing a statement like:
myStruct = new myStructType(someParameters);
to leave myStruct.m holding whatever value it held beforehand.
FWIW Rider says the type is object not object?
Also, Rider will refactor the var into object:
Note: the code will compile even if I manually say object?:
Keep in mind nullable reference types are magic sugar sprinkled on top of the type system. Under the hood there's no difference between object and object?; both refer to System.Object. That magical sugar layer is able to deduce that my object? is not null (because its value came from an interface that promises never to return null).
Being pedantic: there's no difference in the type system between a nullable reference type and the same non-nullable reference type. But there is a difference between nullable and non-nullable value types. To be more precise: double is the System.Double struct, but double? is the System.Nullable<System.Double> struct. MS crammed more magic into the language and compiler to support this bifurcation between reference and value types. This would be a great opportunity to start writing glowing things about the type systems in TypeScript or Rust or any of a dozen other languages.

Why does this program only produce three warnings?

public class Program
{
public static void main(String[] args)
{
string message = "This is a message";
int number = 6;
object obj = null;
int? nullable = (int?)12;
}
}
The first three variable declarations on this program throw the following warning:
The variable 'X' is assigned but its value is never used
Yet, the last statement:
int? nullable = (int?)12;
doesn't throw anything. Why is that?
The warning that you're seeing is only shown in cases where the compiler can prove that the expression used to initialize the variable can't possibly cause any side effects. When you're just assigning a literal string, integer, or null value to a variable, the compiler knows that none of those things can possibly cause side effects. For your last value you're not just assigning a literal value though; you're using the explicit operator of a type as well, and as far as the compiler is concerned, that operator is just some code that could do anything. It could, for example, cause relevant side effects (it doesn't, but the compiler doesn't know that) that would make the line not superfluous.
Warnings are best practice suggestions, they are not true errors. Visual studio is smart enough to see you created the variables but never used them so they are useless. It is recommending that you get rid of them since you don't use them.
If you actually do something with them then the errors will go away. For example if you said:
number += nullable;
Would get rid of 1 of the errors. If you did something like:
message = message + " and this is more message";
It would get rid of the other error.
I believe it sees the int? as an object and since you are casting it to the nullable variable, it can't figure out if it had been used before hand. I think it has something more to do with the casting just isn't caught as an error because it can't tell if you referenced that variable somewhere else.
I think it would be the equivalent of something like this:
var a = new SomeClass();
var b = a;
Since it can't tell if a has really been used, then it doesn't show an error. If you put that in with a real class it will not show the error also.
It seems like the Warning doesn't show because the line with the nullable actually do an operation before the assignation. As for exemple the following code only generate a warning on the variable named number. This seems to be logical in the meaning that the line with warning are truely useless in the current code. The other lines might do something during the execution and will not be "optimized out". To test it run your code in release mode with the debugger ad you will see that all lines that have a warning are skipped ("optimized")
class Program
{
static void Main(string[] args)
{
string message = ';'.ToString();
int number = 6;
object obj = (object)(new t());
int? nullable = (int?)12;
}
class t
{ }
}
With this being said. The nullable it not optimized because there is a cast from a int '12' to a int?. Since it is a real cast and not a "useless cast" an operation is needed during the run time.

"is" operator behaving a bit strangely

1) According to my book, is operator can check whether
expression E (E is type) can be converted to the target type only if E is either a reference conversion, boxing or unboxing. Since in the following example is doesn’t check for either of the three types of conversion, the code shouldn’t work, but it does:
long l; // EDIT - I forgot to add this line of code in my initial post
int i=100;
if (i is long) //EDIT - in my initial post I've claimed condition returns true, but it really returns false
l = i;
2)
a)
B b;
A a = new A();
if (a is B)
b = (B)a;
int i = b.l;
class A { public int l = 100; }
class B:A { }
The above code always causes compile time error “Use of unassigned variable”. If condition a is B evaluates to false, then b won’t be assigned a value, but if condition is true, then it will. And thus by allowing such a code compiler would have no way of knowing whether the usage of b in code following the if statement is valid or not ( due to not knowing whether a is b evaluates to true or false) , but why should it know that? Intsead why couldn’t runtime handle this?
b) But if instead we’re dealing with non reference types, then compiler doesn’t complain, even though the code is identical.Why?
int i = 100;
long l;
if (i is long)
l = i;
thank you
This has nothing to do with the is operator. The compiler sees that there are two possible paths, only one of which will assign a value to b.
When dealing with value types, the compiler knows that l gets implicitly initialized to the value 0.
The real difference is that in the int case, you are talking about the definite assignment of a field (l). Fields are always definitely assigned (even without the =100). In the B case, you are talking about the definite assignment of the local variable (b); local variables do not start as definitely assigned.
That's all it is.
int i=100;
if (i is long) //returns true, indicating that conversion is possible
1: I don't think this returns true at all; for me it shows an IDE warning about never being true. Looking in reflector, the compiler completely removes this branch. I guess the compiler is obliged to at least compile on the grounds that it could (in theory) box and test. But it already knows the answer, so it snips it.
2: I still get the "unassigned variable" compiler error; due to "definite assignment"
The compiler behaves correctly - why should it compile without errors if there is a use of an unassigned variable? You cannot work with b.l if b is unassigned as the compiler checks that there is a code path that does not instantiate b which is why it throws an error ...
In your code, class B derives from A. This means:
a is B // evaluates to false
b is A // evaluates to true
This means that the body of the if block won't be entered, and b will not be assigned.
Stephen Cleary also has a point. I don't know how sophisticated the compiler is when evaluating if values are assigned.
Okay, the MSDN says on is:
The is operator is used to check whether the run-time type of an object is compatible with a given type.
An is expression evaluates to true if both of the following conditions are met:
expression is not null.
expression can be cast to type. That is, a cast expression of the form (type)(expression) will complete without throwing an exception.
That would fit pretty well with 1, but 2 is another topic and correct (think about it).
However, the following code writes 0 to the output:
int i = 1;
long l = 0;
if (i is long) {
l = i;
}
Console.WriteLine(l);
Therefore it seems that the note in the is MSDN documentation is correct as well:
Note that the is operator only considers reference conversions, boxing conversions, and unboxing conversions. Other conversions, such as user-defined conversions, are not considered by the is operator.

Why doesn't the compiler at least warn on this == null

Why does the C# compiler not even complain with a warning on this code? :
if (this == null)
{
// ...
}
Obviously the condition will never be satisfied..
Because you could override operator == to return true for that case.
public class Foo
{
public void Test()
{
Console.WriteLine(this == null);
}
public static bool operator ==(Foo a, Foo b)
{
return true;
}
public static bool operator !=(Foo a, Foo b)
{
return true;
}
}
Running new Foo().Test() will print "True" to the console.
The other question here is: why doesn't the compiler issue a warning for ReferenceEquals(this, null)? From the bottom of the above link:
A common error in overloads of operator == is to use (a == b), (a == null), or (b == null) to check for reference equality. This instead results in a call to the overloaded operator ==, causing an infinite loop. Use ReferenceEquals or cast the type to Object, to avoid the loop.
That might be answered by #Aaronaught's response. And that's also why you should be doing (object)x == null or ReferenceEquals(x, null), not doing a simple x == null, when you're checking for null references. Unless, of course, you're sure that the == operator is not overloaded.
Wow... I guess I was shamefully wrong
I disagree. I think you still make a good point.
The compiler knows whether the comparison is going to go to a user-defined comparison operator or not, and the compiler knows that if it does not, then 'this' is never null.
And in fact, the compiler does track whether a given expression can legally be null or not, in order to implement a minor optimization on non-virtual method calls. If you have a non-virtual method M and you say foo.M(); then the compiler generates this as "do a virtual call to M with receiver foo". Why? Because we want to throw if foo is null, and a virtual call always does a null check on the receiver. A non-virtual call does not; we'd have to generate it as "check foo for null, and then do a non-virtual call to M", which is longer, slower, more irritating code.
Now, if we can get away without doing the null check, we do. If you say this.M() or (new Foo()).M() then we do NOT generate a virtual call. We generate the non-virtual call without the null check because we know that it cannot be null.
So the compiler has excellent data on whether a particular comparison to null will sometimes, always, or never succeed.
The question then is "if the compiler knows that a particular comparison will never succeed, why not generate a warning for it?"
And the answer is "sometimes we do, and sometimes we don't".
We do in this situation:
int x = 123;
if (x == null) ...
There is an equality operator defined on two nullable ints. x is convertible to nullable int. null is convertible to nullable int. So that equality operator is valid, and therefore is used, and is of course always false. The compiler gives a warning that the expression is always false.
However, due to a bug we accidentally introduced in C# 3, this code does NOT produce that warning:
Guid x = whatever;
if (x == null) ...
Same deal. The nullable guid equality operator is valid, but the warning is suppressed. I'm not sure if we fixed this bug for C# 4 or not. If not, hopefully we'll get it into a service pack.
As for "if (this == null)" I don't know why we don't warn for that. It certainly seems like a good candidate for a warning. The most likely explanation is to follow this logical syllogism:
warnings are compiler features
compiler features must be (1) thought of, (2) designed, (3) implemented, (4) tested, (5) documented and (6) shipped to you before you can take advantage of the feature.
No one has done any of those six necessary things for this feature; we can't ship features that we never thought of in the first place.
therefore, no such feature.
There are infinitely many compiler features that we haven't thought of yet; we've implemented none of them.
Another reason to not give a warning here is that we try to give warnings on code which is both likely to be typed by accident and almost certainly wrong for a non-obvious reason. "this == null" is not likely to be typed in by accident, and though it is almost certainly wrong, as you note in the statement of your question, it is obviously wrong.
Compare that to our "guid == null" -- that is likely to happen by accident, because a developer might accidentally think that Guid is a reference type. Guids are usually passed around by reference in C++, so this is an easy mistake to make. The code is almost certainly wrong, but it is wrong in a non-obvious way. So this is a good candidate for a warning. (Which is why it is so unfortunate that this is a warning in C# 2 but not C# 3, due to a bug we introduced.)
Actually, the condition really can be satisfied, at least in Visual Studio 2008. They've fixed this behaviour in VS 2010, but it is not totally inconceivable that there might be another way to create such a condition.
(tl;dr version - in C# 3.5, it's legal to reference this from an anonymous function passed as a constructor argument, but if you actually try to use it, you'll find that this is null.)
Though the code below is a bastard it's still all C#. an if you call Bar you will get an InvalidOperationException with the message null.
public class Foo
{
static Action squareIt;
static Foo() {
var method = new DynamicMethod(
"TryItForReal",
typeof(void),
Type.EmptyTypes,
typeof(Foo).Module);
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Call, typeof(Foo).GetMethod("tryit"));
il.Emit(OpCodes.Ret);
squareIt = (Action)method.CreateDelegate(typeof(Action));
}
public void tryit()
{
if (this == null) {
throw new InvalidOperationException("Was null");
}
}
public void Bar() {
squareIt();
}
}
This also falls in line with other warnings C# does (or not does for that matter) like:
if(true)
or
if(1 == 1)
These will also always have the same result no matter what.

Categories