确定构造函数调用哪个基础构造函数

时间:2017-04-06 22:31:30

标签: c# reflection

我有一系列看起来像

的课程
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) { }
}

我想迭代所有这些类,并根据基础Command类(我无法控制)的四个参数生成信息。理想情况下,我会在运行时执行此操作,以便我们可以向Command添加更多子类,并在下次运行代码时自动显示这些子类。

我知道如何使用反射迭代所有有问题的类。

我知道如何获取每个Type对象并获取构造函数。

我知道如何获取每个ConstructorInfo对象并获取传递给构造函数的参数,包括类型和名称。我需要区分一个byte p2参数的构造函数和一个byte p3参数的构造函数,我可以这样做。

我知道如何获取基类Command类的构造函数,并列出类型和名称(byte p1, byte p2, byte p3, byte[] data)。

如果每个构造函数的主体中都有任何代码,我知道如何使用GetMethodBody()获取它。

但是,我找不到任何方法来告诉每个构造函数实际上正在调用base(byte, byte, byte, byte[])构造函数,而我无法找到任何方法来查看传递的静态值是什么是的。价值观本身就是"魔术"对于基础类而言意味着什么的值,但仅限于组合。 (即0x00, 0x01, 0x00表示一件事,0x01, 0x00, 0x00表示非常不同的东西。)

如何使用反射获取传递给基础构造函数的值?

3 个答案:

答案 0 :(得分:2)

首先,显而易见的答案是你要求做错事。 您应该在派生类上使用属性并查询其内容。像

这样的东西
[Magic(0xDE, 0xAD, 0xBE, 0xEF)]
public class ReadVersionCommand {}

现在已经开始了,为了100%回答你陈述的问题,你可以使用Nuget Package ICSharpCode.Decompiler来支持ILSpy进行一些运行时反编译并准确地告诉你你的问题对于。 因为让它运行是一项繁琐的工作,我为你做了这件事。

输出:

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

代码:

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) { }
    }
}

** 嗯,更像是90%,因为代码不使用Reflection。但是,您可以通过解析ctor的MethodBody中的IL来获得相同的参数。

答案 1 :(得分:1)

反射不允许您以有用的方式检查方法中的实际代码 - 请参阅有关反射的答案:Can I use reflection to inspect the code in a method?

但这是一种应该有效的方法。它使用反射来查找所有子类,然后从每个类的“Prototype”实例中检索所需的数据。

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}");
            }
        }
    }
}

答案 2 :(得分:1)

作为评论中的was suggested,我最终只创建了一个对象实例,然后查询了设置参数。

有效:

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.
它是丑陋的,我并不以此为荣,但它适用于我的目的。

注意:即使这是我这次回答的答案,我也会接受M.Stramm's answer。它绝对是一个比这更好的答案,如果我再次需要这样做,我将使用该解决方案。

相关问题