Я уже писал о новой фиче 5-го Решарпера Call Hierarchy. Логичным развитием Call Hierarchy является Value Tracking. Value Tracking создан для того, чтобы помочь разработчику понять, как в конкретную точку программы могли придти неверные данные или куда эти данные могли уйти. Как следствие, становится легче расследовать причины
Я опять же не буду глубоко теоретизировать, а обзорно покажу как и в каких сценариях работает Value Tracking.
Рассмотрим простой пример. В методе
Если вы самостоятельно запустите анализ на приведенном примере и будите навигироваться по дереву результатов, то обнаружите, что дерево содержит все интересные для программиста узлы:
На этот раз у нас есть интерфейс, его реализации, поля, инициализаторы полей, конструкторы. Попытаемся выяснить, какие значения может выводить на экран метод
В результатах Value Tracking мы видим:
Теперь рассмотрим работу с коллекциями. Попробуем выяснить, множество всех значений, которые будут выведено на экран следующим кодом. Для этого встанем на использование
Мы нашли и явное создание массива, и значения, которые приходят из ленивого энумератора, и даже вызов метода
А теперь попробуем в обратную сторону, посмотрим, куда попадет число
Достаточно быстро мы выяснили, что число
В этом и предыдущем примерах обратите внимание, что как только Value Tracking переходит от отслеживания значения к отслеживанию коллекции, то соответствующие узлы в дереве помечаются специальным розовым значком.
Лямбды, с ними, особенно если их много и не дай бой они вложенные, постоянно возникают проблемы. Давайте посмотрим, как R# работает в следующей ситуации. При этом теперь попробуем прослеживать пусть значений в обе стороны:
Сначала будем искать откуда берутся значения параметра
Попробуем теперь найти, где используется значение возвращаемое лямбдой. Выделяем
Более сложный пример со вложенными ламбдами вы можете легко изготовить сами, заменив вывод на экран (
Анализ по-прежнему будет работать и вы легко выясните, куда попадает значение выражения
NullReferenceException
или неправильное поведение и вывод.Я опять же не буду глубоко теоретизировать, а обзорно покажу как и в каких сценариях работает Value Tracking.
Простой пример
Рассмотрим простой пример. В методе
VisitData
происходит NullReferenceException
, давайте выясним, откуда прихожит null
. Поместите каретку на использование параметра dc
в методе VisitData
и запустите анализ (R# -> 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();
}
}
* This source code was highlighted with Source Code Highlighter.
Если вы самостоятельно запустите анализ на приведенном примере и будите навигироваться по дереву результатов, то обнаружите, что дерево содержит все интересные для программиста узлы:
- Непосредственно использование
dc
(как раз то место, где происходит исключение) - Передача параметра
data
в методаVisitData
- Вызов метода
Do
с «хорошими данными» (в дереве интересующие нас данные подсвечены болдом) - Вызов метода
Do
сnull
– искомая проблема
- Пропускает несущественные шаги, что экономит время
- Показывает данные в удобном виде, т.е. позволяет быстро, не теряя концентрации на проблеме и не отслеживая глазами все использования символов, найти источник проблемы.
Наследование
На этот раз у нас есть интерфейс, его реализации, поля, инициализаторы полей, конструкторы. Попытаемся выяснить, какие значения может выводить на экран метод
Main.Start
. Для этого выделим выражение dataProvider.Foo
и вызовем на нем 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));
}
}
* This source code was highlighted with Source Code Highlighter.
В результатах Value Tracking мы видим:
- Реализацию метода
Foo
, которая возвращает константу1
- Реализацию метода
Foo
, которая возвращает значение поля_foo
, а также все источники значений для этого поля:- Присвоение значения этому полю в конструкторе
- Вызов конструктора с параметром
3
- Инициализатор этого поля со значением
2
Коллекции
Теперь рассмотрим работу с коллекциями. Попробуем выяснить, множество всех значений, которые будут выведено на экран следующим кодом. Для этого встанем на использование
i
внутри Console.WriteLine
и запустим анализ 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;
}
}
* This source code was highlighted with Source Code Highlighter.
Мы нашли и явное создание массива, и значения, которые приходят из ленивого энумератора, и даже вызов метода
Add
. Великолепно!Коллекции в обратную сторону, или куда уходят значения
А теперь попробуем в обратную сторону, посмотрим, куда попадет число
5
. Выделяем его и вызываем 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;
}
}
* This source code was highlighted with Source Code Highlighter.
Достаточно быстро мы выяснили, что число
5
:- Передано в метод
Foo
- Добавляется в коллекцию
- Коллекция возвращается и используется
- Элементы коллекции выводятся на экран
В этом и предыдущем примерах обратите внимание, что как только Value Tracking переходит от отслеживания значения к отслеживанию коллекции, то соответствующие узлы в дереве помечаются специальным розовым значком.
Лямбды
Лямбды, с ними, особенно если их много и не дай бой они вложенные, постоянно возникают проблемы. Давайте посмотрим, как R# работает в следующей ситуации. При этом теперь попробуем прослеживать пусть значений в обе стороны:
public class MyClass
{
void Main()
{
var checkFunction = GetCheckFunction();
Console.WriteLine(checkFunction(1));
}
Func<int, bool> GetCheckFunction()
{
Func<int, bool> myLambda = x =>
{
Console.WriteLine(x);
return x > 100; //искать будем отсюда
};
return myLambda;
}
}
* This source code was highlighted with Source Code Highlighter.
Сначала будем искать откуда берутся значения параметра
x
. Выделяем его использование в вызове Console.WriteLine
и вызываем Value Origin:- Найдена содержащая параметр лямбда
- Далее анализ отследил, куда эта лямбда передается. Обратите внимание, что все узлы, в которых мы отслеживаем лямбду, помечены специальным значком
- На последнем шаге мы видим, что лямбда вызывается с аргументом
1
, это и есть искомое значение дляx
Попробуем теперь найти, где используется значение возвращаемое лямбдой. Выделяем
x>100
, и вызываем Value Destination (R# -> Inspect -> Value Destination):- Анализ отслеживает, что выраженеи возвращается как результат выполнения лямбды
- Далее R# отследил, куда лямбда передавалась
- В конце мы видим вызов метода
WriteLine
, который и использует возвращаемое лямбдой значение
Более сложный пример со вложенными ламбдами вы можете легко изготовить сами, заменив вывод на экран (
Console.WriteLine
) двумя строчками:Func<Func<int, bool>, int, bool> invocator = (func, i) => func(i);
Console.WriteLine(invocator (checkFunction,1));
* This source code was highlighted with Source Code Highlighter.
Анализ по-прежнему будет работать и вы легко выясните, куда попадает значение выражения
x>100
. Код со вложенными лямбдами сильно затруден для понимания обычным человеком, что делает анализ еще более востребованным. Более того, можете попытаться создать коллекцию вложенных лямбд — и это будет работать! Но такие упражнения я оставлю читателю и нелегкой реальной жизни.