Reputation: 7589
In C#, when I call a method, I want to be able to detect if it will (or could potentially) call something with a certain attribute.
For example, when, TheProgram.Run() get's called, I want to know that it will call a MyClass.DoTheWork, which has an attribute [IsRegistered], which calls a private method FormatTheResult() which also has the attribute [IsRegistered].
I've been thinking about it for a while and can't think how it could be achieved. I'm thinking, something like the invert of a stack trace, or registering components with an attribute or aspect, or perhaps leaning on MEF.
Is this possible?
This detection could happen at compile time or a run time, but ideally before the method with the attribute is executed.
Upvotes: 2
Views: 135
Reputation: 166
This is the way I've found to do it:
public static IList<MethodBase> GetCalledMethods(MethodBase methodBase)
{
IList<MethodBase> calledMethods = new List<MethodBase>();
var body = methodBase.GetMethodBody();
Module module = Assembly.GetExecutingAssembly().ManifestModule;
byte[] bytes = body.GetILAsByteArray();
using (var stream = new MemoryStream(bytes))
{
long streamLength = stream.Length;
using (var reader = new BinaryReader(stream))
{
while (reader.BaseStream.Position < streamLength)
{
byte instruction = reader.ReadByte();
if (instruction == OpCodes.Call.Value
|| instruction == OpCodes.Callvirt.Value
|| instruction == OpCodes.Newobj.Value)
{
int token = reader.ReadInt32();
var method = module.ResolveMethod(token);
calledMethods.Add(method);
}
}
}
}
return calledMethods;
}
Upvotes: 0
Reputation: 6546
Resharper does kind of what you want. Execute the menu command Resharper -> Inspect -> Outgoing calls, and then expand tree nodes ad infinitum until you reach the desired method. If you're using reflection or stuff like that, you're out of luck, I guess. The picture below is an example of how it works.
Upvotes: 0
Reputation: 12440
What you are probably looking for is Roslyn.
http://msdn.microsoft.com/en-au/vstudio/roslyn.aspx
What you can do with this is analize the syntax tree directly, so for your method in question you could access from the syntax tree all method calls that occur. Then you can follow that and check the method being called has that attribute.
Is pretty complex stuff, so I wont attempt a code sample for your particular scenario but I have used it before to analize multiple solitions and inject code.
It's pretty awesome here is a sample from the docs.
namespace GettingStartedCS
{
class Program
{
static void Main(string[] args)
{
SyntaxTree tree = SyntaxTree.ParseCompilationUnit(
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}");
var root = (CompilationUnitSyntax)tree.GetRoot();
var firstMember = root.Members[0];
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;
var programDeclaration = (TypeDeclarationSyntax)helloWorldDeclaration.Members[0];
var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0];
var argsParameter = mainDeclaration.ParameterList.Parameters[0];
}
}
}
Upvotes: 1
Reputation: 65069
Mocking frameworks can do this. It is useful for behavioural tests.
For example, given this setup:
public class Calculator {
private IHelpers _helperMethods;
public Calculator(IHelpers helper) {
_helperMethods = helper;
}
public int Add(int a, int b) {
if (_helperMethods.AboveZero(a) && _helperMethods.AboveZero(b)) {
return a + b;
}
throw new Exception("Argument not above zero");
}
}
public interface IHelpers {
bool AboveZero(int i);
}
Using Moq, you can verify (via a behavioural unit test) that IHelpers.AboveZero
is called when calling the Add
method like so:
[TestMethod]
public void When_Add_Called_Verify_AboveZero_Called_Too() {
// Arrange
var helperMock = new Mock<IHelpers>();
helperMock.Setup(x => x.AboveZero(It.IsAny<int>())).Returns(true);
var calc = new Calculator(helperMock.Object);
// Act
var result = calc.Add(1, 2);
// Assert
helperMock.Verify(x => x.AboveZero(It.IsAny<int>())); // verify that AboveZero was called.
}
The attributes are a different story though..
Is this what you were after?
(Please excuse any compiler errors.. this was typed by hand :/)
Upvotes: 2