Ev.
Ev.

Reputation: 7589

Is there anyway to detect what will be called from a method?

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

Answers (4)

Derek J S
Derek J S

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

Gebb
Gebb

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.

enter image description here

Upvotes: 0

shenku
shenku

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

Simon Whitehead
Simon Whitehead

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

Related Questions