You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 6控制台程序:如何移除Switch实现参数与Action动态映射

移除.NET 6控制台应用中的Switch语句,实现动态Action关联

问题背景

现有一个.NET 6控制台应用,通过命令行参数触发对应操作。当前使用switch语句匹配参数与Action类,新增操作时必须修改switch分支,缺乏动态性。目标是让开发人员只需编写实现IAction接口的Action类,无需修改主逻辑,程序自动完成参数与Action的关联。

原主程序代码:

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // validating args
            if (!args.Any())
                return;

            switch (args.First())
            {
                case "action1":
                case "action2":
                case "action3":
                    break;

                default:
                    return;
            }

            switch (args.First())
            {
                case "action1":
                    RunTheAction<Action1>();
                    break;

                case "action2":
                    RunTheAction<Action2>();
                    break;

                case "action3":
                    //todo RunTheAction<Action3>()
                    break;

                // it keeps growing -> RunTheAction<>()
            }
        }

        static void RunTheAction<T>() where T : IAction
        {
            var action = Activator.CreateInstance<T>();
            action.Execute();
        }
    }
}

IAction接口及实现示例:

internal interface IAction
{
    void Execute();
}

internal class Action1 : IAction
{
    public void Execute()
    {
        // do some action1 stuff
    }
}

internal class Action2 : IAction
{
    public void Execute()
    {
        // do some action2 stuff (different than action1 :) )
    }
}

解决方案

方案1:自定义特性+反射扫描

通过特性标记Action类对应的命令参数,程序启动时自动扫描所有IAction实现类,建立参数与类型的映射。

步骤1:定义命令特性

[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
internal class ActionCommandAttribute : Attribute
{
    public string CommandName { get; }

    public ActionCommandAttribute(string commandName)
    {
        CommandName = commandName;
    }
}

步骤2:给Action类添加特性

[ActionCommand("action1")]
internal class Action1 : IAction
{
    public void Execute()
    {
        // do some action1 stuff
    }
}

[ActionCommand("action2")]
internal class Action2 : IAction
{
    public void Execute()
    {
        // do some action2 stuff
    }
}

步骤3:重构主程序逻辑

internal class Program
{
    static void Main(string[] args)
    {
        if (!args.Any())
            return;

        var commandActionMap = GetCommandActionMap();
        if (!commandActionMap.TryGetValue(args.First(), out var actionType))
            return;

        RunTheAction(actionType);
    }

    // 扫描程序集,构建命令与Action类型的映射
    static Dictionary<string, Type> GetCommandActionMap()
    {
        var map = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
        var actionTypes = typeof(IAction).Assembly.GetTypes()
            .Where(t => typeof(IAction).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);

        foreach (var type in actionTypes)
        {
            var attr = type.GetCustomAttribute<ActionCommandAttribute>();
            if (attr != null && !string.IsNullOrEmpty(attr.CommandName))
            {
                map[attr.CommandName] = type;
            }
        }

        return map;
    }

    // 执行指定类型的Action
    static void RunTheAction(Type actionType)
    {
        if (Activator.CreateInstance(actionType) is IAction action)
        {
            action.Execute();
        }
    }
}

方案2:约定优先(无需额外特性)

如果命令参数名称与Action类名(小写)一致,可直接通过类名约定建立映射,无需添加特性,实现更简洁。

重构主程序逻辑

internal class Program
{
    static void Main(string[] args)
    {
        if (!args.Any())
            return;

        var commandActionMap = GetCommandActionMap();
        if (!commandActionMap.TryGetValue(args.First(), out var actionType))
            return;

        RunTheAction(actionType);
    }

    // 基于类名约定构建映射:类名转小写作为命令参数
    static Dictionary<string, Type> GetCommandActionMap()
    {
        var map = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
        var actionTypes = typeof(IAction).Assembly.GetTypes()
            .Where(t => typeof(IAction).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);

        foreach (var type in actionTypes)
        {
            var commandName = type.Name.ToLowerInvariant();
            map[commandName] = type;
        }

        return map;
    }

    static void RunTheAction(Type actionType)
    {
        if (Activator.CreateInstance(actionType) is IAction action)
        {
            action.Execute();
        }
    }
}

此方案下,Action1对应命令参数action1,Action2对应action2,新增Action类后自动生效。

方案3:依赖注入(适合复杂场景)

若后续Action需要依赖其他服务,可使用.NET依赖注入容器管理Action实例,便于扩展和测试。

步骤1:引入依赖注入包

在项目中安装Microsoft.Extensions.DependencyInjection NuGet包。

步骤2:重构主程序逻辑

using Microsoft.Extensions.DependencyInjection;

internal class Program
{
    static void Main(string[] args)
    {
        if (!args.Any())
            return;

        // 构建服务容器并注册所有IAction实现
        var services = new ServiceCollection();
        RegisterActions(services);

        using var serviceProvider = services.BuildServiceProvider();

        // 根据命令参数查找对应的Action实例
        var command = args.First();
        var action = serviceProvider.GetServices<IAction>()
            .FirstOrDefault(a => a.GetType().Name.ToLowerInvariant() == command);

        action?.Execute();
    }

    // 注册所有IAction实现类
    static void RegisterActions(IServiceCollection services)
    {
        var actionTypes = typeof(IAction).Assembly.GetTypes()
            .Where(t => typeof(IAction).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);

        foreach (var type in actionTypes)
        {
            services.AddTransient(typeof(IAction), type);
        }
    }
}

若需要自定义命令名,可结合方案1的特性,修改FirstOrDefault中的判断逻辑即可。

方案对比

  • 特性标记:灵活性最高,支持自定义命令名,适合命令名与类名不一致的场景;
  • 约定优先:实现最简单,无额外代码,适合命令名与类名一一对应的场景;
  • 依赖注入:适合需要注入服务的复杂场景,便于单元测试和扩展。

内容的提问来源于stack exchange,提问作者DevSurferPTPXO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 16:12:04