使用反射来查找类并正确调用

时间:2017-11-19 06:06:31

标签: c# reflection

我有一个名为Action的抽象类,它接受一个字节数组并表示客户端执行的操作。我有几个从这个类实现的动作,它们包含属性,如:

[Action(ActionCode.Login)]
public class LoginAction : Action
{
    public string Username { get; private set; }
    public string Password { get; private set; }

    public LoginAction(byte[] data)
        : base(data)
    {
        this.Username = this.ReadMapleString();
        this.Password = this.ReadMapleString();
    }
}

我希望能够使用如下操作来定义方法:

public static void Login(LoginAction action)

因此,当我从客户端收到数据时,我可以根据收到的代码处理操作。但是,我不确定如何使用反射来查找与操作相关联的方法。我的意思是,我可以使用下面的代码找到LoginAction,但我找不到使用LoginAction作为参数的方法,这是我想要调用的方法。

我希望实现以下目标:

public void OnRecieveData(ActionCode code, byte[] data)
{
    // Using the ActionCode, call the specified action handler method.
    // Login(new LoginAction(data));
}

我已经知道如何查找使用ActionCodeAttribute的类,但我不知道如何调用它:

static IEnumerable<Type> GetTypesWithHelpAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(HelpAttribute), true).Length > 0) {
            yield return type;
        }
    }
}

3 个答案:

答案 0 :(得分:1)

假设您的设计有充分的理由(请参阅注释),您可以通过一些LINQ找到它。

首先,我们需要找到类型。我们只获取所有类型,按类型过滤它们的属性(使用OfType<>),并找到至少有一个ActionAttribute的第一个类。

var type = System.Reflection.Assembly.GetCallingAssembly()
            .GetTypes()
            .Where
            (
                t => t.GetCustomAttributes()
                      .OfType<ActionAttribute>()
                      .Where( a => a.ActionCode == code)
                      .Any()
            )
            .Single();

接下来找到静态成员。我们已经知道了包含类的类型。但我们不一定知道这个名字。再一次,我们知道第一个参数的类型,并且调用是静态的。假设总有一种方法符合所有这些标准,我们可以使用:

var member = type
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
            .Where
            ( 
                m=> m.GetParameters()
                     .First()
                     .ParameterType == type
            )
            .Single();

然后我们创建实例:

var instance = Activator.CreateInstance(type, new object[] { data });

并调用它:

member.Invoke(null, new object[] { instance });

完整示例:

    static public void OnReceiveData(ActionCode code, byte[] data)
    {
        var type = System.Reflection.Assembly.GetCallingAssembly()
            .GetTypes()
            .Where
            (
                t => t.GetCustomAttributes()
                      .OfType<ActionAttribute>()
                      .Where( a => a.ActionCode == code)
                      .Any()
            )
            .Single();

        var member = type
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
            .Where
            ( 
                m=> m.GetParameters()
                     .First()
                     .ParameterType == type
            )
            .Single();

        var instance = Activator.CreateInstance(type, new object[] { data });

        member.Invoke(null, new object[] { instance });
    } 

Working example on DotNetFiddle

答案 1 :(得分:0)

当您想要基于代码执行操作时,您可以执行的操作是创建包含操作对象的字典:

public abstract class Action {
    public abstract void Execute(byte[] data);
}

//...

Dictionary<ActionCode, Action> actions = new Dictionary<ActionCode, Action>();

//...

void ExecuteAction(ActionCode actionCode, byte[] data) {
    actions[actionCode].Execute(data);
}

您可以以解决问题的适当方式填充actions字典。如果要从外部库加载实现,可以执行以下操作(使用现有的GetTypesWithHelpAttribute方法):

void PopulateActions(Assembly assembly) {
    foreach(Type actionType in GetTypesWithHelpAttribute(assembly)) {
        var codeQry =
            from attribute in type.GetCustomAttributes().OfType<ActionAttribute>()
            select attribute.Code;
        ActionCode code = codeQry.Single();
        Action action = (Action)Activator.CreateInstance(type);
        actions.Add(code, action);
    }
}

假设您将HelpAttribute应用于要加载的操作。您也可以直接查找ActionAttribute并完全删除HelpAttribute

此解决方案将反射代码保留在它所属的位置:从外部源加载操作时。您的执行代码没有反射,这提高了可维护性和性能。您还可以将动态加载与添加的静态操作相结合,而无需从主模块反射。

答案 2 :(得分:0)

如果您乐意为同一类中的每种不同类型的操作定义使用相同名称的方法:

void Handle(LoginAction action);
void Handle(FooAction action);
void Handle(BarAction action);

你已经构建了你的......正确类型的Action对象,你可以使用它来调用它:

Handle((dynamic)action)

如果你只是做

,你甚至可以负担明确的案例
dynamic dynAction = action;
Handle(dynAction);

是的,正确的重载将在运行时根据动作的类型确定。