我有一系列看起来像
的课程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
表示非常不同的东西。)
如何使用反射获取传递给基础构造函数的值?
答案 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。它绝对是一个比这更好的答案,如果我再次需要这样做,我将使用该解决方案。