Dotnet logo

.NET Tools

Essential productivity kit for .NET and game developers

How-To's

Introducing ReSharper 5.0: Value Tracking

Previous posts introducing ReSharper 5.0:

Recently we posted about Call Tracking, which is a new feature in ReSharper 5.0.

A logical next step from Call Tracking is Value Tracking. Value Tracking is designed to help you determine how a certain incorrect value might have passed to a given point in your program, and where it might be passed next. This helps you investigate possible NullReferenceExceptions, inappropriate behavior and reasons why you get incorrect values.

You can call Value Tracking by choosing ReSharper | Inspect | Value Origin or ReSharper | Inspect | Value Destination over a local variable, parameter, or another kind of value, and you can also use a new shortcut to Call Tracking, Value Tracking, and Type Hierarchy: Inspect This (Ctrl+Shift+Alt+A).

Without theorizing too much, I will review some use cases and scenarios where Value Tracking is useful.

A Basic Example

In this example, a NullReferenceException is thrown in the VisitData method. Let’s determine where the null comes from. Position the caret at the usage of the dc parameter in VisitData and run the analysis (ReSharper | Inspect | Value Origin):

    public class Main
    {
      void Start()
      {
        Console.WriteLine(Do(new DataClass()));
      }

      void Start2()
      {
        Console.WriteLine(Do(null));
      }

      int Do(DataClass data)
      {
        var v = new Visitor();
        return v.VisitData(data);
      }
    }

    public class DataClass
    {
      public int GetData()
      {
        return 0;
      }
    }

    public class Visitor
    {
      public int VisitData(DataClass dc)
      {
        return dc.GetData();
      }
    }

If you run the analysis in your own code (using the same example) and try to navigate around the results tree, you will see that it contains all the nodes you might be interested in:

  • Our usage of dc (the place the exception is thrown).
  • The data parameter passed to VisitData
  • A call of Do with “good data” (in the graphic above, the values we need are in bold).
  • A call of Do with the null value – the culprit.

Basically, this is a massive Find Usages. However, Value Tracking:

  • Skips the unimportant steps, thus saving time.
  • Displays values in a convenient way, so you can find the source of your problem without looking through all the symbol usages one by one.

Value Tracking is especially useful if variable names are changed often, or if values are placed in collections or passed via closures. Let’s look at these more complex use cases in more detail.

Inheritance

So we have an interface, its implementation, fields, field initializers, and constructors. Our task is to figure out which values Main.Start can display. Highlight dataProvider.Foo and invoke Value Origin on it (ReSharper | Inspect | Value Origin):

    public interface IInterface
    {
      int Foo();
    }

    public class Base1 : IInterface
    {
      public virtual int Foo()
      {
        return 1;
      }
    }

    public class Base2 : IInterface
    {
      private readonly int _foo = 2;

      public Base2()
      {  
      }

      public Base2(int foo)
      {
        this._foo = foo;
      }

      public virtual int Foo()
      {
        return _foo;
      }
    }

    public class Main
    {
      public void Start(IInterface dataProvider)
      {
        Console.WriteLine(dataProvider.Foo());
      }

      public void Usage()
      {
        Start(new Base2(3));
      }
    }


The results of this Value Tracking include:

  • Implementation of Foo that returns the constant 1.
  • Implementation of Foo that returns the value of _foo as well as all value sources of that field:
    • The assignment of a value to the field in the constructor.
    • Call of constructor with parameter = 3.
    • Initializer of the field with value = 2.

Note how in just a few seconds we found all the possible values. Imagine how much time you would save on highly-branched hierarchies and complex logic!

Collections

Now let’s look at working with collections. Let’s identify the set of all values that the following code will print to screen. To do this, position the caret on the usage of i inside Console.WriteLine and run the Value Origin analysis (ReSharper | Inspect | Value Origin):

   class Main
   {
     void Foo()
     {
       var list = new List<int>();
       list.AddRange(GetData());
       list.AddRange(GetDataLazy());
       ModifyData(list);

       foreach (var i in list)
       {
         Console.WriteLine(i);
       }
     }

     void ModifyData(List<int> list)
     {
       list.Add(6);
     }

     private IEnumerable<int> GetData()
     {
       return new[] { 1, 2, 3 };
     }

     IEnumerable<int> GetDataLazy()
     {
       yield return 4;
       yield return 5;
     }
   }

We found an explicit creation of the array, the values that come from the lazy enumerator, and even the Add call. Awesome!

Collections backwards, or where values pass to

Now let’s try going the other direction and see where the number 5 passes. Highlight it and run Value Destination (ReSharper | Inspect | Value Destination):

   public class testMy
   {
     void Do()
     {
       int x = 5;
       var list = Foo(x);

       foreach (var item in list)
       {
         Console.WriteLine(item);
        }
     }

     List<int> Foo(int i)
     {
       var list = new List<int>();
       list.Add(i);
       return list;
     }
   }

We find rather quickly that:

  • 5 is passed to Foo
  • 5 is added to the collection.
  • The collection is returned and used.
  • Collection elements are printed to screen.

Note how, in this and earlier examples, tree nodes are marked with a special pink icon whenever Value Tracking switches from tracking a value to tracking a collection.

Lambdas

Lambdas tend to cause problems, especially if there are many of them or they’re nested. Let’s see how ReSharper handles the following situation. We’re going to track the value paths in both directions:

   public class MyClass
   {
     void Main()
     {
       var checkFunction = GetCheckFunction();
       Console.WriteLine(checkFunction(1));
     }

     Func<Func<int, bool>, int, bool> GetCheckFunction()
     {
       Func<Func<int, bool>, int, bool> myLambda = x =>
                  {
                   Console.WriteLine(x);
                   return x > 100; // this is where we start searching from
                  };
       return myLambda;
     }
   }

First, let’s find out where the values of x come from. Highlight its usage in the Console.WriteLine call and invoke Value Origin (ReSharper | Inspect | Value Origin):

  • We found the lambda that contains the parameter.
  • Next, the analysis tracked where this lambda was passed. Note how all the nodes in which we track the lambda are marked with a special icon.
  • On the last step, we see that the lambda is called with the argument of 1. This is the value of x we’re looking for.

Let’s find out where the value that the lambda returned is used. Highlight x>100 and invoke Value Destination (ReSharper | Inspect | Value Destination):

  • The analysis identifies that the expression is returned when the lambda is executed.
  • Next, ReSharper tracks where the lambda was passed.
  • Finally, we see a WriteLine call that uses the value returned by the lambda.

You can come up with a more complex example using nested lambdas, by replacing the Console.WriteLine call with these two lines:

    Func<Func<int, bool>, int, bool> invocator = (func, i) => func(i);
    Console.WriteLine(invocator (checkFunction,1));

The analysis will still work, and you will easily see where the value of x>100 is passed.

Code that uses nested lambdas is difficult to understand for most people, making this kind of analysis even more useful. You can even try to create a collection of nested lambdas — and it will still work! Go ahead and try it… if you’re into hardcore fun!

Author: Alexander Zverev, senior ReSharper developer. Translated from original article (in Russian)

image description