如何在C#中按属性搜索方法?

时间:2019-03-10 19:02:37

标签: c# interface attributes

我正在用C#编写.NET Core命令行实用程序。我想允许用户基于命令行参数指定要运行的“操作”。我模仿的是PowerShell样式的命令行选项,所以我的选项之一是/Action。因此,例如,用户可以使用/Action:Update/Action:Reset来调用应用。

在C#中,每个操作都有一个方法,该方法遵循特定的方法签名。因此,对于上面的Update方法,我有一个像这样的方法:public static int Update(Dictionary<string,string> cmdLineArgs, SomeObject obj)。与/Action上的有效参数相关的每种方法都具有完全相同的签名(相同的类型和变量名称)。

现在我只有一个switch块来调用操作,但这似乎效率很低:

int returnValue;
switch (parsedArgs["action"]) {
    case "update":
        returnValue = Update(parsedArgs, o);
        break;
    case "reset":
        returnValue = Reset(parsedArgs, o);
        break;
    ...
    default:
        returnValue=255;
        Console.WriteLine($"No such action {parsedArgs["action"]}.");
        break;
}

我已经在Web API的上下文中使用了属性,并且它们似乎是使其变得更通用的自然起点。理想情况下,这样的情况将导致添加新动作就像编写其方法并添加具有用户可以在/Action开关中调用的名称的正确属性一样简单。我的想法是创建一个自定义属性(例如AppActionName),然后将该属性放在可以从命令提示符处称为操作的任何方法上:

[AppActionName("update")]
public static int Update(Dictionary<string,string> cmdLineArgs, SomeObject obj)
...

[AppActionName("reset")]
public static int Reset(Dictionary<string,string> cmdLineArgs, SomeObject obj)
...

我考虑过的另一种利用类型安全性的方法是使用定义操作方法的接口:

public interface IAppAction 
{
    int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj);
}

[AppActionName("update")]
public class UpdateAction : IAppAction
{
    public int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj)
    ...

[AppActionName("reset")]
public class ResetAction : IAppAction
{
    public int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj)
    ...

不过,无论哪种情况,我都不知道如何实际搜索,实例化和运行该方法。

在第一个选项中(将AppActionName直接放在方法上),我看到两个问题:1)必须弄清楚如何在程序集中的所有方法中搜索具有给定属性的方法,进行过滤,然后实际进行搜索。调用该方法,然后2)除非我不知道该怎么做,否则我认为我无法使用此方法强制执行正确的方法签名。

int returnValue;
// in other languages you can get a variable and then call it, but this isn't other languages
// but you might do something like: myMethod = findMethodWithAttribute("update"); returnValue=myMethod(parsedArgs, o);

第二个选项(类实现接口上的接口)似乎更类型安全,并且应该更易于实现(声明一个接口变量,然后将其分配给正确的类的实例),但是我仍然没有确定如何实际搜索具有正确名称的属性。

int returnValue;
// how would you do this correctly?
IAppAction appActionClass = new FindTheClassWithTheAttributeWithParameter("update")();
returnValue = appActionClass.Run(parsedArgs, o);

因此,我认为我的问题的实质是:“如何查找使用我指定的参数定义的属性的方法/类,然后如何实例化/调用结果?”

1 个答案:

答案 0 :(得分:1)

第二种方法通常应该更容易处理。

我举了一个示例(您可以运行here)来获取实现接口及其属性的类。

给出类似这样的内容:

public class AppActionNameAttribute : Attribute 
{ 
    public string Action { get; set; } 

    public AppActionNameAttribute(string action) { Action = action; } 
}

public interface IAppAction 
{
    int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj);
}

[AppActionName("update")]
public class UpdateAction : IAppAction
{
    public int Run(Dictionary<string,string> cmdLineArgs, SomeObject obj) 
    { 
        Console.WriteLine("Handled :)"); 
        return 1;
    }
}

public class SomeObject { }

您可以这样做:

var handlers = typeof(Program).Assembly
     // Get all types in the assembly
    .GetExportedTypes()
     // that are classes and implement IAppAction
    .Where(x => x.IsClass && x.GetInterface("IAppAction") != null)
    .Select(x => new 
    {
        // assuming they are always decorated with [AppActionName]
        Action = x.GetCustomAttribute<AppActionNameAttribute>().Action,
        // get a new instance, assuming parameterless constructor
        Handler = (IAppAction)Activator.CreateInstance(x)
    })
    // and convert it to a Dictionary that you can easily use
    .ToDictionary(x => x.Action, x => x.Handler);

您可以存储(最好是在某个静态字段中,因为您不想经常运行它)并像这样简单地使用:

private static Dictionary<string, IAppAction> _handlers = ...;

public static void Main()
{
    string action = Console.ReadLine();

    // you should check the action actually exists in the Dictionary
    var handler = _handlers[action];

    // and then run it:
    Console.WriteLine(handler.Run(someData, otherData));
}