代码之家  ›  专栏  ›  技术社区  ›  fdmillion

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

  •  3
  • fdmillion  · 技术社区  · 6 年前

    我正在用C#for编写一个命令行实用程序。净核心。我希望允许用户根据命令行参数指定要运行的“操作”。我在模仿PowerShell风格的命令行选项,所以我的选项之一是 /Action 。例如,用户可以使用 /Action:Update /Action:Reset

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

    现在我有一个 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的上下文中使用了属性,它们似乎是使其更通用的自然起点。理想的情况是,添加新操作非常简单,只需编写其方法,并使用用户可以在中调用的名称添加正确的属性即可 /行动 转换我的想法是创建一个自定义属性(例如, 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 回复  |  直到 6 年前
        1
  •  2
  •   Camilo Terevinto Chase R Lewis    6 年前

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

    我举了一个例子(你可以运行 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));
    }