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. Код со вложенными лямбдами сильно затруден для понимания обычным человеком, что делает анализ еще более востребованным. Более того, можете попытаться создать коллекцию вложенных лямбд — и это будет работать! Но такие упражнения я оставлю читателю и нелегкой реальной жизни.
