In this case the compiler detects that s is written but not read, and deliberately suppresses the warning.
The reason is because C# is a garbage-collected language, believe it or not.
How do you figure that?
Well, consider the following.
You have a program which calls a method DoIt() that returns a string. You do not have the source code for DoIt(), but you wish to examine in the debugger what its return value is.
Now in your particular case you are using DoIt() for its side effects, not its return value. So you say
DoIt(); // discard the return value
Now you're debugging your program and you go to look at the return value of DoIt() and it's not there because by the time the debugger breaks after the call to DoIt(), the garbage collector might have already cleaned up the unused string.
And in fact the managed debugger has no facility for "look at the thing returned by the previous method call". The unmanaged C++ debugger has that feature because it can look at the EAX register where the discarded return value is still sitting, but you have no guarantee in managed code that the returned value is still alive if it was discarded.
Now, one might argue that this is a useful feature and that the debugger team should add a feature whereby returned values are kept alive if there's a debugger breakpoint immediately following a method execution. That would be a nice feature, but I'm the wrong person to ask for it; go ask the debugger team.
What is the poor C# developer to do? Make a local variable, store the result in the local variable, and then examine the local in the debugger. The debugger does ensure that locals are not garbage collected aggressively.
So you do that and then the compiler gives you a warning that you've got a local that is only written to and never read because the thing doing the reading is not part of the program, it's the developer sitting there watching the debugger. That is a very irritating user experience! Therefore we detect the situation where a non-constant value is being assigned to a local variable or field that is never read, and suppress that warning. If you change up your code so that instead it says string s = "hello";
then you'll start getting the warning because the compiler reasons, well, this can't possibly be someone working around the limitations of the debugger because the value is right there where it can be read by the developer already without the debugger.
That explains that one. There are numerous other cases where we suppress warnings about variables that are never read from; a detailed exegisis of all the compiler's policies for when we report warnings and when we do not would take me quite some time to write up, so I think I will leave it at that.