This topic is related to this question on StackOverflow.
Let's discover, what are CanBeNull
or NotNull
attributes? How they work?
These attributes provided by JetBrains ReSharper Annotations and help you to find errors in your code with ReSharper.
First of all - definition of NotNullAttribute
and CanBeNullAttribute
:
namespace JetBrains.Annotations
{
/// <summary>
/// Indicates that the value of the marked element could never be <c>null</c>.
/// </summary>
/// <example><code>
/// [NotNull] object Foo() {
/// return null; // Warning: Possible 'null' assignment
/// }
/// </code></example>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method |
AttributeTargets.Property | AttributeTargets.Field |
AttributeTargets.Event | AttributeTargets.Interface |
AttributeTargets.Parameter | AttributeTargets.Delegate |
AttributeTargets.GenericParameter)]
[Conditional("JETBRAINS_ANNOTATIONS")]
public sealed class NotNullAttribute : Attribute
{
}
}
namespace JetBrains.Annotations
{
/// <summary>
/// Indicates that the value of the marked element could be <c>null</c> sometimes,
/// so the check for <c>null</c> is necessary before its usage.
/// </summary>
/// <example><code>
/// [CanBeNull] object Test() => null;
///
/// void UseTest() {
/// var p = Test();
/// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'
/// }
/// </code></example>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method |
AttributeTargets.Property | AttributeTargets.Field |
AttributeTargets.Event | AttributeTargets.Interface |
AttributeTargets.Parameter | AttributeTargets.Delegate |
AttributeTargets.GenericParameter)]
[Conditional("JETBRAINS_ANNOTATIONS")]
public sealed class CanBeNullAttribute : Attribute
{
}
}
Their definition is equal and marked as Conditional("JETBRAINS_ANNOTATIONS")
. This is important, when you create some library: if you don't define JETBRAINS_ANNOTATIONS
variable, compiler will ignore all attributes and you won't get binary reference to JetBrains.Annotations.dll
.
Sample project:
namespace NotNullProject
{
class Program
{
static void Main([CanBeNull] string[] args)
{
var obj = new TestClass();
string value = null;
obj.Property = value;
obj.Property = string.Empty;
Console.WriteLine(obj.Property);
}
private class TestClass
{
private string _property;
[NotNull]
public string Property
{
get { return _property; }
set { _property = value; }
}
}
}
}
We have Property
declared with NotNull
attribute in TestClass
. Let's create instance of the class and assign Property
to null. This code compiles without errors. Maybe, we will get a runtime exception? No, NotNull
attribute does not affect compilation process and does not add any additional runtime checks.
But ReSharper tells us about potential problem:
NotNull
with condition checking:
static void Main([CanBeNull] string[] args)
{
var obj = new TestClass();
if (obj.Property == null)
{
Console.WriteLine("wrong");
}
}
ReSharper says that expression if always false
because of NotNull
attribute and can be removed. But this program will output wrong
to console! So, be carefull. Annotating with NotNull
does not guarantee that value is not null in runtime.
Can we make property that never returns null but can accept it?
public string Property
{
[NotNull]
get { return _property ?? string.Empty; }
set { _property = value; }
}
ReSharper does not tell anything. So, annotate property instead of getter or setter.
Attribute works inside property too:
[NotNull]
public string Property
{
get
{
return null;
}
set
{
if (value == null)
{
Console.WriteLine("null");
}
_property = value;
}
}
Setter is a method with one parameter. Can we tell ReSharper that this parameter is not null with attribute target?
public string Property
{
get { return _property; }
[param: NotNull]
set { _property = value; }
}
No, does not work;
Equivalent can be written with methods:
private class TestClass
{
private string _property;
public void SetProperty([NotNull] string value)
{
_property = value;
}
public string GetProperty()
{
return _property;
}
}
Conclusion
I expected more powerful support of usage these attributes with properties: atribute target, getters and setters annotationg. But we have to mark property only.