1

I have a whole series of classes which look like

public class ReadVersionCommand : Command
{
    public ReadVersionCommand() : base(0x00, 0x01, 0x00, null) { }
}
public class DisplayTextCommand : Command
{
    public DisplayTextCommand(byte[] data) : base(0x05, 0x00, 0x00, data) { }
}
public class ReadKeyCommand : Command
{
    public ReadKeyCommand(byte p3, byte[] data) : base(0x09, 0x00, p3, data) { }
}

I want to iterate over all these classes, and generate information based on the four parameters to the base Command class (which I don't have control over). Ideally, I'd do this at runtime, so that we can add more subclasses to Command and have them automatically show up the next time we run the code.

I know how to use reflection iterate all the classes in question.

I know how to take each Type object and get the constructor.

I know how to take each ConstructorInfo object and get the parameters passed to the constructor, both the types and the names. I need to differentiate between a constructor which has one byte p2 parameter and one which has one byte p3, and I can do that.

I know how to get the base Command class's constructor, and list the types and names (byte p1, byte p2, byte p3, byte[] data).

If there were any code in the body of each constructor, I know how to get it with GetMethodBody().

However, I can't find any way to tell that each constructor is actually calling the base(byte, byte, byte, byte[]) constructor, and I can't find any way to see what the static values which are being passed to are. The values themselves are "magic" values which mean things to the underlying class, but only in combination. (i.e. 0x00, 0x01, 0x00 means one thing, and 0x01, 0x00, 0x00 means something very different.)

How can I get the values passed to the base constructor using reflection?

Bobson
  • 13,498
  • 5
  • 55
  • 80
  • What are you actually asking? Does the compiler enforce the compiler's rules? Yes, it does. You've specified a base constructor in your code so it couldn't possibly be invoking anything else. I suspect that you are actually asking a different question and as such, you should update your question to be more explicit. – David L Apr 06 '17 at 22:39
  • @DavidL Fundamentally, I want my code to tell me *"If you use this `DisplayTextCommand` constructor, then the first parameter to `Command`'s constructor will be `0x05`."* This is the one piece of that process I can't find. – Bobson Apr 06 '17 at 22:41
  • My follow-up question would be, what else could it possibly be? – David L Apr 06 '17 at 22:46
  • @DavidL - With a different subclass of `Command`, or a different constructor of `DisplayTextCommand`, it may be `0x00`, or `0x20`, or any other byte. I want to be able to tell this for *any* subclass of `Command`. – Bobson Apr 06 '17 at 22:47
  • Reflection allows you to get information about the *structure* of code at runtime, but it doesn't go so far as to decompile it into an abstract syntax tree that you can pull apart. What's your specific use-case? If `DisplayTextCommand` is your own class, then you'd be better to pull out the values into literals which you can use from elsewhere. If it's a class in someone else's library, then could you create a static instance and query the values from that? – ben Apr 06 '17 at 23:03
  • @ben - I've rewritten it to hopefully be clearer. I might be able to create an instance and query that, and I'm actually experimenting with that while I wait to see if anyone can help, but even if I work around it now, I'd like to know the answer. It seems like one can get at just about everything else with Reflection, but I can't find this piece. – Bobson Apr 06 '17 at 23:17
  • Are those values stored? Could you create an instance of each object and reflect the variable where those values are stored? – MrZander Apr 06 '17 at 23:27
  • You are asking two different questions. Getting the information which ctor is called is easily available using the `GetMethodBody` method. Getting which values are passed in a particular call is not possible without an AST, like already said in the comments. – thehennyy Apr 07 '17 at 08:29
  • 3
    Reflection is the wrong tool for the job here. There should be a way to use Roslyn to get the symbolic information for the base clause. – Eric Lippert Apr 07 '17 at 08:48

3 Answers3

2

First off, the obvious answer is that you are asking for the wrong thing. You should use attributes on your derived classes and query for their contents. Something like

[Magic(0xDE, 0xAD, 0xBE, 0xEF)]
public class ReadVersionCommand {}

Now that that's out of the way, to answer your stated problem 100%** you can use the Nuget Package ICSharpCode.Decompiler which powers ILSpy to do some runtime decompilation and give you exactly what you asked for. Because it's a fiddly bit of work to make it run, I did that for you.

Output:

public ReadVersionCommand ();
If you use this ReadVersionCommand constructor, then the first parameter to Command's constructor will be 19

public ReadVersionCommand (byte b2);
If you use this ReadVersionCommand constructor, then the first parameter to Command's constructor will be 5

public ReadVersionCommand (byte b1, byte b2);
If you use this ReadVersionCommand constructor, then the first parameter to Command's constructor will be b1

Code:

namespace ConsoleApp1 {
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using ICSharpCode.Decompiler;
    using ICSharpCode.Decompiler.Ast;
    using ICSharpCode.NRefactory.CSharp;
    using Mono.Cecil;

    class Program {
        static void Main(string[] args) {
            var path = Assembly.GetExecutingAssembly().Location;
            var assembly = AssemblyDefinition.ReadAssembly(path);

            var types = assembly.Modules.SelectMany(x => x.Types).ToList();

            var baseType = types.FirstOrDefault(x => x.FullName == typeof(Command).FullName);
            var derivedTypes = types.Where(x => x.BaseType == baseType);

            var ctx = new DecompilerContext(assembly.MainModule);
            foreach (var type in derivedTypes) {
                var astBuilder = new AstBuilder(ctx);
                astBuilder.AddType(type);

                var ast = astBuilder.SyntaxTree;
                var ctorDecls = ast.Descendants.OfType<ConstructorDeclaration>();

                var descriptors = ctorDecls.Select(ctor => Describe(type, ctor));
                foreach (var desc in descriptors) {
                    var firstParameter = desc.BaseCallParameters.FirstOrDefault();

                    Console.WriteLine(desc.Signature);
                    Console.WriteLine($"If you use this {desc.Type.Name} constructor, then the first parameter to {baseType.Name}'s constructor will be {firstParameter}");
                    Console.WriteLine();
                }

                Console.ReadLine();
            }
        }

        private static string GetPrettyCtorName(ConstructorDeclaration ctor) {
            var copy = ctor.Clone();
            var blocks = copy.Children.OfType<BlockStatement>().ToList();
            foreach (var block in blocks) {
                block.Remove();
            }

            return copy.ToString().Replace(Environment.NewLine, "");
        }

        private static ConstructorDescriptor Describe(TypeDefinition type, ConstructorDeclaration ctor) {
            return new ConstructorDescriptor {
                Type = type,
                Signature = GetPrettyCtorName(ctor),
                BaseCallParameters =
                            ctor
                            .Descendants
                            .OfType<MemberReferenceExpression>()
                            .Where(y => y.ToString() == "base..ctor")
                            .Select(y => y.Parent)
                            .FirstOrDefault()
                            ?.Children
                            .Skip(1)

            };
        }
    }

    public class ConstructorDescriptor {
        public TypeDefinition Type { get; set; }
        public string Signature { get; set; }
        public IEnumerable<AstNode> BaseCallParameters { get; set; }
    }

    public class Command {
        public Command(byte b1, byte b2, byte b3, byte[] data) { }
    }

    public class ReadVersionCommand : Command {
        public ReadVersionCommand() : base(0x13, 0x37, 0x48, null) { }

        public ReadVersionCommand(byte b2) : base(0x05, b2, 0x00, null) { }

        public ReadVersionCommand(byte b1, byte b2) : base(b1, b2, 0x00, null) { }
    }
}

** Well, more like 90% since the code does not use Reflection. You could however achieve the same by parsing the IL in the MethodBody of the ctor to get the parameters.

M.Stramm
  • 1,289
  • 15
  • 29
  • 1
    +1 for using attributes on the class, which are accessible via reflection to solve OP's actual problem. Another +1 (if i could) for answering the question as it was asked, despite it being entirely superfluous :-) – ben Apr 07 '17 at 12:42
  • I like this answer better than what I ended up doing. If I ever need to revisit this, or use it again later, and I can't redesign to avoid the need in the first place, I'll definitely use this instead. Thanks! – Bobson Apr 07 '17 at 19:32
1

Reflection doesn't let you inspect the actual code inside a method in a helpful way - see this answer regarding reflection: Can I use reflection to inspect the code in a method?.

But here's an approach that should work. It uses reflection to find all the subclasses, and then retrieves the data you want from a "Prototype" instance of each class.

public abstract class Command
{
    // define a public property for each element you want to query
    public byte Data { get; }

    public Command(byte data)
    {
        Data = data;
    }
}

public class Command1 : Command
{
    // Require each subclass to define a static "prototype" instance,
    // calling the constructor with default values for any args
    public static Command Prototype = new Command1();

    public Command1() : base(0x12)
    {
    }
}

[TestFixture]
public class ReflectionTest
{
    [Test]
    public static void ListPrototypes()
    {
        // find all loaded subclasses of Command
        var subclasses =
            from assembly in AppDomain.CurrentDomain.GetAssemblies()
            from type in assembly.GetTypes()
            where type.IsSubclassOf(typeof(Command))
            select type;
        foreach (var subclass in subclasses)
        {
            // get the prototype instance of each class
            var prototype =
                subclass.GetField("Prototype", BindingFlags.Public | BindingFlags.Static)?.GetValue(null) as Command;
            if (prototype != null)
            {
                // emit the data from the prototype
                Console.WriteLine($"{subclass.Name}, Data={prototype.Data}");
            }
        }
    }
}
Community
  • 1
  • 1
ben
  • 1,441
  • 2
  • 16
  • 21
1

As was suggested in the comments, I ended up just creating an instance of the object then querying the set parameters.

Effectively:

ctor = type.GetConstructor();
parameters = ctor.GetParameters();
foreach (p in parameters)
{
   // Mark that we have this parameter
}
// Construct array of parameters, using garbage values.
ctor.Invoke(callingParameters);
// For each parameter we didn't have, read the value.

It's ugly, and I'm not proud of it, but it works for my purposes.

Note: Even though this is the answer I went with this time, I'm going to accept M.Stramm's answer instead. It's definitely a better answer than this, and if I ever need to do this again, I will be using that solution instead.

Community
  • 1
  • 1
Bobson
  • 13,498
  • 5
  • 55
  • 80