- ReSharper and optimistic analysis
- Get a clue!
- Using Annotations
- Referencing the assembly directly
- Copying the annotations to your project locally
- CanBeNull, when ReSharper assumes (optimistically) that an object will not be null
- CanBeNull, when ReSharper assumes (unwisely) that an object CAN’T be null
- NotNull, when ReSharper determines that accessing a null value will throw an exception
Anyone who uses ReSharper is familiar with the little squiggly line indicating a “warning” of one kind or another. And anyone who develops with .NET is familiar with the ubiquitous NullReferenceException and ReSharper’s “possible ‘System.NullReferenceException'” warning.
This post was inspired by a question on SO: ReSharper: Null check is always false warning
ReSharper and optimistic analysis
Here’s a really simple example (VS 2013 / R# 8.2) demonstrating the feature. In both cases, a string is initialized to null and then we conditionally set it to a value, before displaying the length of the string.
In this first snippet, ReSharper’s analysis detects that “name” will always *be null, and underlines it to warn that accessing the “Length” property will *possibly throw an exception. In this case, we know it will.
In the next snippet, ReSharper’s analysis detects that name will *never *be null, so no underline. (It also grays out the first initialization, as it’s never used).
Now I’ll remove part of the code to a separate method. Obviously, a NullReferenceException will still be thrown, but ReSharper no longer warns us.
The reason for the above behavior is that ReSharper can’t afford to traverse all paths and evaluate all outcomes, on-the-fly. It’d be too expensive. Yes, in this case it’s only a single method. But what about methods nested 10 deep, which then call to another assembly that needs to be decompiled, and ultimately makes a service call to some API out on the Interwebz?
Instead, ReSharper uses “optimistic analysis”, which means that when it can’t determine the outcome, it doesn’t show the warning. If it were a pessimistic analyzer, it’d display the warning when it wasn’t sure. Either way, it amounts to guessing, and sometimes we may want to help it be more accurate.
Get a clue!
What if we had a way to help ReSharper? We do, actually. ReSharper can’t determine whether the method could possibly return null, but we can give it a nudge nudge wink wink, using some special attributes from JetBrains (also referred to as “annotations” or “contracts”).
Notice the addition of the “CanBeNull” attribute in the above code. We’ve told ReSharper that the method might return null, so now it can warn other developers that happen to call it.
Note that it only works one level deep. Marking a nested method which will ultimately cause a NullReferenceException does not affect the top-level call.
Annotations (aka “contracts”, aka attributes) like the one above are supplied by JetBrains. They created them, so we could give ReSharper a heads-up.
Referencing the assembly directly
If you don’t mind referencing another assembly, you can reference JetBrains.Annotations directly. On my machine, the file is located at:
C:\Program Files (x86)\JetBrains\ReSharper\v8.2\Bin\JetBrains.Annotations.dll
You’ll need to reference the assembly in your files too. In C#, for example, don’t forget to add:
Copying the annotations to your project locally
If you do not want, or are not allowed, to reference the assembly directly (or you want to modify the annotations for your own use), JetBrains has provided another method for using them.
Open the ReSharper menu in VS, click on Options, and find the “Code Annotations” tab on the left. Use the button on the right to copy the annotations to your clipboard.
Paste them into a new class file. The name of the file doesn’t matter, but the namespace does… sort of. ReSharper will offer to modify the namespace to fit in with the rest of your solution, but if you do that and make no other changes, ReSharper won’t respect the attribute while analyzing your code. You could add one more line above the namespace:
// ReSharper disable once CheckNamespace namespace JetBrains.Annotations
Alternatively, you could modify the namespace and then open the Annotations settings again. ReSharper will recognize the location of the annotations (here I buried them in a couple of nested directories) and give you the option of selecting that location. Do so and save the settings, and it will respect the annotations in the new location.
This gives you access to dozens of other attributes with which to adorn your code.
The two that seem most useful in a variety of situations are the CanBeNull and NotNull attributes.
CanBeNull, when ReSharper assumes (optimistically) that an object will not be null
One example of CanBeNull was shown above:
While some annotations have limited usage (like AspRequiredAttribute, which only applies to classes), others can be applied in a variety of ways. Either look up the documentation online, or use a tool like JetBrains’ dotPeek to read the source:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Delegate, AllowMultiple = false, Inherited = true)] public sealed class CanBeNullAttribute : Attribute
Here’s another example, where the annotation has been applied to a parameter. Again, ReSharper doesn’t know whether or not “myString” could possibly be null, so it optimistically assumes it won’t be. But we can annotate the parameter to hint to ReSharper that “myString” may very well be null. Now we get a warning.
CanBeNull, when ReSharper assumes (unwisely) that an object CAN’T be null
I’ve been a long-time user of ReSharper, but only stumbled on annotations while searching for a solution to this question. The OP copied NHibernate’s NullableDictionary class into his project, and noticed ReSharper warning about the following section of code:
Normally, you can’t have a null key in a Dictionary. From MSDN:
As long as an object is used as a key in the, it must not change in any way that affects its hash value. Every key in a must be unique according to the dictionary’s equality comparer. A key cannot be *null.* (my emphasis)
ReSharper is assuming that, since the class implements IDictionary, the key passed to TryGetValue cannot be null. (You can verify this by deleting the IDictionary interface from the class signature… the warning disappears.) I suppose it’s a cost-effective assumption, since most dictionaries would not implement a null key, but it’s certainly not a safe one.
Implementation details are left up to the class implementing an interface, so assumptions like this should not be made based solely on the interface being implemented. This is reflected in the docs for IDictionary, also on MSDN:
Implementations can vary in whether they allow key to be null.
Here, we’ve got a class where the key can be null, and we want to let ReSharper know it. There are a few ways we could do this. One way is a special comment, which you can add by clicking the light bulb in the left margin, after placing the cursor on the offending piece of code. Personally, not my favorite. I don’t need more comments, and it leaves the code grayed out.
Instead, we can use annotations again, this time marking the parameter as “CanBeNull”, so ReSharper knows that all of this code is very reachable:
NotNull, when ReSharper determines that accessing a null value will throw an exception
For the sake of completeness, here’s the NotNull attribute in action. Accessing the value of a nullable int, without first checking whether or not it has a value, will throw an exception, and ReSharper is warning us. By marking the called method with the NotNull attribute, we are telling ReSharper that the method will never return a null, so the warning is unnecessary. (Silly example, as you’d change the return value in the signature instead, but it demonstrates the usage.)
You can read more about the ReSharper NullReferenceException analysis, and related annotations/contracts, at JetBrains’s website.
You can also read more about the various annotations, their descriptions and a few sample usages.