3

In C# how can I evaluate if any members of a class contain switch case or if else constructs? I want to check in a unit test if a class has been written with either switch...case or chained if....else.

I know how to get members of a class using reflection, but cannot find an example on the interwebs of how to get the actual code. From this SO post I found you can use MethodBase.GetMethodBody(), here. That seems great for returning a MethodBody for getting variables, but cannot find how to get information of if a switch...case or if...else exists.

Any solutions available?

Community
  • 1
  • 1
Dib
  • 2,001
  • 2
  • 29
  • 45
  • 1
    Did you see the [`MethodBody.GetILAsByteArray()` Method](https://msdn.microsoft.com/en-us/library/system.reflection.methodbody.getilasbytearray.aspx)? – Rowland Shaw Feb 13 '17 at 08:50
  • 1
    Are you looking for *any* control flow or just those two constructs specifically? By the time it's been compiled into IL, those constructs no longer exist. So if you want to target just those constructs, you'll be trying to reverse engineer from the IL. – Damien_The_Unbeliever Feb 13 '17 at 08:51
  • @RowlandShaw - I skimmed over it as I wasn't really sure how it would help. I take it there will be some specific IL that I can search for then. Am I correct that both `switch...case` and `if...else` both compile to same IL instructions? – Dib Feb 13 '17 at 08:53
  • @Damien_The_Unbeliever - I don't need to know control flow, just if either of the constructs were used in the method. I assume from what you say that wont be possible? – Dib Feb 13 '17 at 08:54
  • 2
    I don't think that is possible. Compiler may translate those construct to diiferent IL statements or the same, depending on several things. See [this answer](http://stackoverflow.com/a/395965/579895) for example – Pikoh Feb 13 '17 at 08:56
  • @Pikoh - Aha - I see. Interesting how the compiler is optimised to compile different code based upon those kind of conditions. An interesting read. – Dib Feb 13 '17 at 08:59
  • 1
    If you have pdb, I think that's possible, but not a trivial thing. You can dig deep into [IlSpy](https://github.com/icsharpcode/ILSpy) to see how it manages to reverse engeneer the code and extract the text of a method. – 3615 Feb 13 '17 at 09:14
  • @3615 - No I don't have access to the .pdb files. Reimplmenting ILSpy may be a bit much! :) – Dib Feb 13 '17 at 09:29
  • 2
    Just to be pedantic, that's not really an appropriate use for a unit test. You are talking about code inspection, the easiest way to do this is with code reviews. Another way to do it is to write a VS/Roslyn addin that parses the AST - like Resharper does. – slugster Feb 13 '17 at 09:32
  • @slugster - Fair comment but this isn't for production code, but for Kata exercises. The only way the Kata can be checked is through "NUnit" unit tests. – Dib Feb 13 '17 at 09:38

1 Answers1

3

You can't do it with reflection. Yes, you can get the method IL byte array but it's useless for your requirement.

The best way to achieve what you need is to use Roslyn, then it can't be more simple.

bool ContainsIfElseOrSwitchTest()
{
    var classToTest = // you can get it from your VS solution 
                      // or by reading the .cs file from disk
    // for example
    classToTest = CSharpSyntaxTree.ParseText(File.OpenRead(pathToFile));

    return classToTest.GetRoot().DescendantNodes().
              Any(node => node is SwitchStatementSyntax || node is IfStatementSyntax);
}

Update the answer, based on comment.

Other option is to use Mono.Cecil to get directly IL instructions without using byte array. But you must know that you can just know if the instructions contains conditions and you can't know if it's if else or switch.

Other option, is of course to parse the text in yourself and find what you want..

Dudi Keleti
  • 2,946
  • 18
  • 33
  • That would be great but I don't have any choice with the compiler. Its - `C# 6.0 (Mono JIT compiler version 4.0.1 installed via mono-complete package)` – Dib Feb 13 '17 at 09:31
  • I have checked MonoCecil, as parsing text would be preferred, however I don't have access to include any references or add any new packages. It is a shame as I don't need to know if it is specifically `if...else` or `switch...case`, but if either has been used. I'm coming to the conclusion with how my wrists are bound there is not going to be a solution. – Dib Feb 13 '17 at 09:46
  • 1
    @Dib If you really don't care how to do it, but just get results, maybe you really parse it yourself. Because `if` `else` and `switch` are c# words, you can easily check it it exist inside a method block (but not as comment). – Dudi Keleti Feb 13 '17 at 09:54
  • Is there away to get the method "code" as text out using `System.Reflection` or some other out-of-the-box solution? I have not been able to find a `GetMethodText()` type of method on any objects – Dib Feb 13 '17 at 09:56
  • 1
    @Dib You can't get text out of the box. Just parse it :\. If you want to do that with IL translation and you can't use `Cecil`, check my other SO answers [this](http://stackoverflow.com/a/40634343/986184) or [this](http://stackoverflow.com/a/40258432/986184) for examples of how to do that, but I tell you, it's not the fun way. – Dudi Keleti Feb 13 '17 at 10:06
  • 1
    Well, if the IL stream has the `0x45` opcode in there and it was written with a current C# compiler then it's safe to assume the source had a `switch`, but the inverse doesn't hold as C# may also compile a `switch` to the equivalent of a chain of `if`-`else`, two or more `switch` in an `if`-`else` and another .NET language compiler might do something very different again. – Jon Hanna Feb 13 '17 at 10:13
  • 1
    @JonHanna Sure. I warned him about that, but he just want to know if one of them are used so it doesn't matter if it `switch` or `if...else` or maybe even `flag ? expr : otherExpr` – Dudi Keleti Feb 13 '17 at 10:19
  • There's two many things that are a branch in CIL and not in the source C# or vice versa for that. – Jon Hanna Feb 13 '17 at 10:26
  • @JonHanna Completely agree. It's is the last option and I don't recommend it ,It's really not the way to do it, he will can identify just branching and not the type of branching. I think the parsing way is not so bad option if its not for "real" code and just for exercise. – Dudi Keleti Feb 13 '17 at 10:34
  • Parsing would be great and WILL work for me, but how do can you actually get at the actual code that was written without access to the PDB files? How can I get the actual text that makes up the code which is compiled? Surely by the time the Unit Test is accessing the method it is compiled and all text written has long since been converted into IL? – Dib Feb 14 '17 at 10:28
  • @Dib I didn't notice that you don't have the source or .pdb's. If that the case, and you can't use Roslyn or Cecil or any other lib, the only way is to look on the links that I wrote in previous comment and try to handle it with IL analysis. If you will need a specific help with this, let me know. Anyway, remember that it really not how you should do that and I offer it just as exercise. – Dudi Keleti Feb 14 '17 at 10:58
  • 1
    @DudiKeleti - I did try to get my head around the code in your links, and you have an assembly too, which I did down load to see if that would help. It looks like I don't have much choice due to the restrictions i have a round me, so will try and get my head around your example and use that. Good work, BTW! – Dib Feb 14 '17 at 11:11