0

In the code below, the last two references to key are flagged by Resharper, even though Resharper ought to know that key will not be null.

First, the string property is check for Null, Empty, or Whitespace and the block skipped if any of those conditions are true. The extension method, ToLowerNullSafe() will only return Null if the input is Null - and this is marked with an Annotation (2nd code block). Since we've already checked that the property is non-null and the extension method is marked to return non-null, I would expect Resharper to know that key is non-null.

var myObj = new { MyProperty = "some string" };
var myCache = new Dictionary<string, object>();

if (!string.IsNullOrWhiteSpace(myObj.MyProperty))
{
    string key = myObj.MyProperty.ToLowerNullSafe();
    lock (myCache)
    {
        if (!myCache.ContainsKey(key))
        {
            myCache.Add(key, myObj);
        }
    }
}

These two lines above are where key is flagged:

if (!myCache.ContainsKey(key))

and

myCache.Add(key, myObj);

Here is the ToLowerNullSafe method, with its annotation.

[ContractAnnotation("null => null; notnull => notnull")]
public static string ToLowerNullSafe(this string str)
{
    return string.IsNullOrWhiteSpace(str) ? str : str.ToLower();
}

Any ideas on why Resharper appears to be ignoring the annotations? And how do I fix it?

Using:

  • Visual Studio Ultimate 2013 Update 5
  • Resharper 8.2.3
  • Current Annotations from Nuget (both code and external) are applied to the project.
  • .Net 4.5.2

EDIT

Redundant Null Check on myObj.MyProperty Adding a redundant null check (myObj.MyProperty != null) either before or after the !string.IsNullOrWhiteSpace check clears the error.

if (myObj.MyProperty != null && !string.IsNullOrWhiteSpace(myObj.MyProperty))
{
    string key = myObj.MyProperty.ToLowerNullSafe();
    lock (myCache)
    {
        if (!myCache.ContainsKey(key))
        {
            myCache.Add(key, myObj);
        }
    }
}

Redundant Null Check on key Adding a redundant check, assertion, or contract assumption can also clear the error.

Here I've added Contract.Assume(key != null); immediately after assigning a value to key.

if (!string.IsNullOrWhiteSpace(myObj.MyProperty))
{
    string key = myObj.MyProperty.ToLowerNullSafe();
    Contract.Assume(key != null);
    lock (myCache)
    {
        if (!myCache.ContainsKey(key))
        {
            myCache.Add(key, myObj);
        }
    }
}

Not calling ToLowerNullSafe This also clears the error. But since the ContainsKey method performs case-sensitive comparisons where I want a case-insensitive comparison, there could be problems.

if (!string.IsNullOrWhiteSpace(myObj.MyProperty))
{
    string key = myObj.MyProperty;
    lock (myCache)
    {
        if (!myCache.ContainsKey(key))
        {
            myCache.Add(key, myObj);
        }
    }
}

Calling the built-in string function ToLower This also clears the error. But would make the code base inconsistent. We have many places in the code where null values can be expected and must be safely handled, so to avoid problems we've applied custom ...NullSafe extension methods throughout our code. I'm not sure if that matches a 'best practice', but it is what we've done on this project.

if (!string.IsNullOrWhiteSpace(myObj.MyProperty))
{
    string key = myObj.MyProperty.ToLower();
    lock (myCache)
    {
        if (!myCache.ContainsKey(key))
        {
            myCache.Add(key, myObj);
        }
    }
}

With all the working code examples, it seems that Resharper is simply ignoring the Contract Annotations on the ToLowerNullSafe extension method. However, in the first working example, it does recognize those annotations, but by using an explicit and redundant null check on the object's property.

Zarepheth
  • 2,465
  • 2
  • 32
  • 49
  • 1
    As an aside, it might be useful to create the dictionary using a case insensitive string comparer, rather than the default comparer, which is case sensitive. Then you wouldn't need to call `ToLower` at all. See [this SO question for more](http://stackoverflow.com/questions/13230414/case-insensitive-access-for-generic-dictionary) – citizenmatt Sep 02 '16 at 08:40
  • @citizenmatt I'll have to investigate that suggestion. – Zarepheth Sep 06 '16 at 15:27

2 Answers2

2

Can you give this a try

[ContractAnnotation("str:null => null; str:notnull=>notnull")]

I was searching why Resharper would do this and found this page, it could also be a version issue from Resharper?

Edit

I have implemented the same as you and made the extension method exactly from your code and the squiggly line disappear on the "Key" when I do the following with the Annotation.

[ContractAnnotation("null => true; notnull => true")]

Hope that helps.

Sarel Louw
  • 378
  • 2
  • 11
  • Since there is only one parameter, including the parameter name is optional. Anyway, I tried your suggestion, but it did not make any difference. – Zarepheth Sep 01 '16 at 16:09
  • I don't know if it matters, but the extension method is in a different project of the solution. Both projects have the same version of Resharper, the Resharper code annotation, and the Resharper external annotations. Resharper's default external annotations ought to recognize the `string.IsNullOrWhiteSpace` call, while my use of `[ContractAnnotation]` ought to be recognized on the `ToLowerNullSafe` method. – Zarepheth Sep 01 '16 at 16:11
  • I think why its because the ContainsKey returns a bool and the annotation doing the same now if i am saying it correctly now. Also learning these things now... :) – Sarel Louw Sep 01 '16 at 16:47
1

I suspect this is a bug in ReSharper 8 (especially given the answer by @sarel-louw which returns a bool in the contract for a string return). I've just tried this in ReSharper 2016.2, and it doesn't flag the key variables at all - the ContractAnnotation isn't needed.

citizenmatt
  • 18,085
  • 5
  • 55
  • 60
  • Until management decides to provide funding for an upgrade (or better yet, a full subscription), I'm stuck with ReSharper 8.2.3. – Zarepheth Sep 06 '16 at 15:26