.NET Tools
Essential productivity kit for .NET and game developers
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 NullReferenceException
s, 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)