⬆️ ⬇️

ReSharper: Call Hierarchy

ReSharper 5.0 has a new Call Hierarchy feature. In essence, it is a convenient UI for Bulk Find Usages or Go To Declaration.



Initially, in the article I wanted to make a comparative analysis of this feature in R # and VS 2010, but in the process of writing it was found that the Call Hierarchy in VS 2010 does not hold water (does not work with events, interfaces, closures, etc.) and with examples from the article does not show anything useful and reasonable. So I’ll just talk about interesting things that the Call Hierarchy can do in R #.





Events



Let's try to look for outgoing calls from the Foo method ( R # -> Inspect -> Outgoing ):

')

using System;



public class C2

{

public event EventHandler E = (sender, args) => Subscription_In_Initializer();



static void Subscription_In_Initializer()

{

}



void Foo()

{

E( this , EventArgs .Empty);

}

}



class C3

{

void Bar()

{

new C2().E += Subscription_In_Method;

}



void Subscription_In_Method( object sender, EventArgs e)

{

}

}




* This source code was highlighted with Source Code Highlighter .








The result is clear and understandable. R # safely finds all event E subscriptions and displays them as possible call options. Nothing extraordinary, but very convenient.



Generics



Consider a simple code example:



public abstract class Base<T>

{

public void Do(T value )

{

DoImplementation( value );

}



protected abstract void DoImplementation(T value );

}



public class Concrete1 : Base< int >

{

protected override void DoImplementation( int value )

{

}

}



public class Concrete2 : Base< string >

{

protected override void DoImplementation( string value )

{

}

}





* This source code was highlighted with Source Code Highlighter .




Now let's look for outgoing calls from Base.Foo :







Everything is obvious.



Now add this Main class and try to look for outgoing calls from Foo :



class Main

{

void Foo()

{

Concrete2 c = null ; // null,

c.Do( "string" );

}

}




* This source code was highlighted with Source Code Highlighter .








Concrete1.DoImplementation is no longer a challenge! That's because R # looked at the type parameters and concluded that Base.Do called with T->string (see the second line of results: Base<string>.Do - string indicates that we are calling a method with a specific substitution ), and accordingly filtered Concrete1 , because it uses inheritance with the substitution T->int .



Now a slightly more complex, but even more vital example with the Visitor pattern. Find incoming (Incoming) calls to ConcreteVisitor1.VisitNode1 ( R # -> Inspect -> Incoming Calls ). Please note that in this example we are already going in the opposite direction, as if against method calls:



public interface IVisitor<T>

{

void VisitNode1(T data);

}



class Node1

{

public void Accept<T>(IVisitor<T> v, T data)

{

v.VisitNode1(data);

}

}



public class ConcreteVisitor1 : IVisitor< int >

{

public void VisitNode1( int data)

{

}

}



public class ConcreteVisitor2 : IVisitor< string >

{

public void VisitNode1( string data)

{

}

}



public class C1

{

void Foo()

{

var v = new ConcreteVisitor1();

new Node1().Accept(v, 1);

}



void Foo2()

{

var v = new ConcreteVisitor2();

new Node1().Accept(v, "string" );

}

}





* This source code was highlighted with Source Code Highlighter .




The result will be:







Passing through the generic visitor, R # did not lose information about the substitution of type parameters and was able to filter out an extra call from the Foo2 method. In cases where you have a spreading hierarchy and a large number of generic types, this logic allows you to drastically reduce the search area.



Constructors



Now a somewhat artificial example with field constructors and initializers. We are looking for outgoing calls from the constructor of the class Derived :



class Base

{

public Base()

{

Base_Bar();

}



void Base_Bar()

{

}

}



class Derived : Base

{

int _i = Foo();



public Derived()

{

Bar();

}



void Bar()

{

}



static int Foo()

{

return 0;

}

}





* This source code was highlighted with Source Code Highlighter .








Again, nothing unusual. R # simply displays the natural order of calls, not forgetting the implicit call of the base constructor. This will allow an inexperienced developer to save a lot of time on understanding the code, and will be useful for the weary experienced.



Value Tracking. Instead of a conclusion.



And for a snack. If you look in the menu R # -> Inpect , you will find two very interesting items “Value Origin” and “Value Destination”. These two functions implement Data Flow Analysys, i.e. allow you to track where it came from or where the value of a variable or parameter will go. Naturally, DFA works with collections and delegates (it tracks that the item was taken from the collection and goes on to look for references to the collection) and is absolutely indispensable when looking for the reasons for a NullReferenceException . Unfortunately, this will require me to have a whole bunch of screenshots and examples, so I will write more about it in the next article, but for now you can try DFA yourself and tell you what you liked and what didn't.

Source: https://habr.com/ru/post/83769/



All Articles