Skip to content

todo cli.cs

文件信息

  • 📄 原文件:01_todo_cli.cs
  • 🔤 语言:csharp

完整代码

csharp
// ============================================================
//                      项目实战:命令行 Todo 应用
// ============================================================
// 综合运用 C# 特性:类、接口、LINQ、异步、JSON、文件 IO
// 功能:添加/完成/删除/列出/搜索 Todo 项
// 数据持久化:JSON 文件存储
// 运行:dotnet run -- add "学习 C#"
//       dotnet run -- list
//       dotnet run -- done 1
//       dotnet run -- delete 1
//       dotnet run -- search "C#"

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

// ============================================================
//                      数据模型
// ============================================================

enum Priority { Low, Medium, High }

class TodoItem
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public bool IsCompleted { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.Now;
    public DateTime? CompletedAt { get; set; }
    public Priority Priority { get; set; } = Priority.Medium;

    [JsonIgnore]
    public string StatusIcon => IsCompleted ? "✓" : "○";

    [JsonIgnore]
    public string PriorityIcon => Priority switch
    {
        Priority.High => "🔴",
        Priority.Medium => "🟡",
        Priority.Low => "🟢",
        _ => "⚪"
    };

    public override string ToString()
    {
        string timeStr = IsCompleted && CompletedAt.HasValue
            ? $"完成于 {CompletedAt:MM-dd HH:mm}"
            : $"创建于 {CreatedAt:MM-dd HH:mm}";
        return $"[{Id:D3}] {StatusIcon} {PriorityIcon} {Title} ({timeStr})";
    }
}

// ============================================================
//                      存储层
// ============================================================

interface ITodoStorage
{
    Task<List<TodoItem>> LoadAsync();
    Task SaveAsync(List<TodoItem> todos);
}

class JsonFileStorage : ITodoStorage
{
    private readonly string _filePath;
    private readonly JsonSerializerOptions _options;

    public JsonFileStorage(string filePath)
    {
        _filePath = filePath;
        _options = new JsonSerializerOptions
        {
            WriteIndented = true,
            Converters = { new JsonStringEnumConverter() }
        };
    }

    public async Task<List<TodoItem>> LoadAsync()
    {
        if (!File.Exists(_filePath))
            return new List<TodoItem>();

        string json = await File.ReadAllTextAsync(_filePath);
        return JsonSerializer.Deserialize<List<TodoItem>>(json, _options)
               ?? new List<TodoItem>();
    }

    public async Task SaveAsync(List<TodoItem> todos)
    {
        string json = JsonSerializer.Serialize(todos, _options);
        await File.WriteAllTextAsync(_filePath, json);
    }
}

// ============================================================
//                      业务逻辑层
// ============================================================

class TodoService
{
    private readonly ITodoStorage _storage;
    private List<TodoItem> _todos = new();
    private int _nextId = 1;

    public TodoService(ITodoStorage storage)
    {
        _storage = storage;
    }

    public async Task InitializeAsync()
    {
        _todos = await _storage.LoadAsync();
        _nextId = _todos.Count > 0 ? _todos.Max(t => t.Id) + 1 : 1;
    }

    public async Task<TodoItem> AddAsync(string title, Priority priority = Priority.Medium)
    {
        var item = new TodoItem
        {
            Id = _nextId++,
            Title = title,
            Priority = priority
        };
        _todos.Add(item);
        await _storage.SaveAsync(_todos);
        return item;
    }

    public async Task<bool> CompleteAsync(int id)
    {
        var item = _todos.FirstOrDefault(t => t.Id == id);
        if (item is null || item.IsCompleted) return false;

        item.IsCompleted = true;
        item.CompletedAt = DateTime.Now;
        await _storage.SaveAsync(_todos);
        return true;
    }

    public async Task<bool> DeleteAsync(int id)
    {
        var item = _todos.FirstOrDefault(t => t.Id == id);
        if (item is null) return false;

        _todos.Remove(item);
        await _storage.SaveAsync(_todos);
        return true;
    }

    public IReadOnlyList<TodoItem> GetAll() => _todos.AsReadOnly();

    public IEnumerable<TodoItem> Search(string keyword)
        => _todos.Where(t => t.Title.Contains(keyword, StringComparison.OrdinalIgnoreCase));

    public IEnumerable<TodoItem> GetPending()
        => _todos.Where(t => !t.IsCompleted).OrderByDescending(t => t.Priority);

    public IEnumerable<TodoItem> GetCompleted()
        => _todos.Where(t => t.IsCompleted).OrderByDescending(t => t.CompletedAt);

    public (int Total, int Completed, int Pending) GetStats()
    {
        int total = _todos.Count;
        int completed = _todos.Count(t => t.IsCompleted);
        return (total, completed, total - completed);
    }
}

// ============================================================
//                      命令行界面层
// ============================================================

class TodoCli
{
    private readonly TodoService _service;

    public TodoCli(TodoService service)
    {
        _service = service;
    }

    public async Task RunAsync(string[] args)
    {
        if (args.Length == 0)
        {
            PrintHelp();
            return;
        }

        string command = args[0].ToLower();

        switch (command)
        {
            case "add":
                await HandleAddAsync(args[1..]);
                break;
            case "list" or "ls":
                HandleList(args[1..]);
                break;
            case "done" or "complete":
                await HandleCompleteAsync(args[1..]);
                break;
            case "delete" or "del" or "rm":
                await HandleDeleteAsync(args[1..]);
                break;
            case "search" or "find":
                HandleSearch(args[1..]);
                break;
            case "stats":
                HandleStats();
                break;
            case "help" or "--help" or "-h":
                PrintHelp();
                break;
            default:
                Console.WriteLine($"未知命令: {command}");
                PrintHelp();
                break;
        }
    }

    private async Task HandleAddAsync(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("错误: 请提供 Todo 标题");
            Console.WriteLine("用法: add <标题> [--priority high|medium|low]");
            return;
        }

        Priority priority = Priority.Medium;
        string title = args[0];

        // 解析选项
        for (int i = 1; i < args.Length; i++)
        {
            if ((args[i] == "--priority" || args[i] == "-p") && i + 1 < args.Length)
            {
                priority = args[i + 1].ToLower() switch
                {
                    "high" or "h" => Priority.High,
                    "low" or "l" => Priority.Low,
                    _ => Priority.Medium
                };
                i++;
            }
        }

        var item = await _service.AddAsync(title, priority);
        Console.WriteLine($"已添加: {item}");
    }

    private void HandleList(string[] args)
    {
        string filter = args.Length > 0 ? args[0].ToLower() : "all";

        IEnumerable<TodoItem> items = filter switch
        {
            "pending" or "p" => _service.GetPending(),
            "completed" or "done" or "c" => _service.GetCompleted(),
            _ => _service.GetAll().OrderBy(t => t.Id)
        };

        var list = items.ToList();
        if (!list.Any())
        {
            Console.WriteLine("暂无 Todo 项");
            return;
        }

        Console.WriteLine($"\n📋 Todo 列表({filter}):");
        Console.WriteLine(new string('-', 60));
        foreach (var item in list)
            Console.WriteLine(item);
        Console.WriteLine(new string('-', 60));

        var (total, completed, pending) = _service.GetStats();
        Console.WriteLine($"共 {list.Count} 项 | 总计 {total} | 已完成 {completed} | 待完成 {pending}");
    }

    private async Task HandleCompleteAsync(string[] args)
    {
        if (args.Length == 0 || !int.TryParse(args[0], out int id))
        {
            Console.WriteLine("用法: done <ID>");
            return;
        }

        bool success = await _service.CompleteAsync(id);
        Console.WriteLine(success ? $"✓ 已完成 #{id}" : $"未找到 #{id} 或已完成");
    }

    private async Task HandleDeleteAsync(string[] args)
    {
        if (args.Length == 0 || !int.TryParse(args[0], out int id))
        {
            Console.WriteLine("用法: delete <ID>");
            return;
        }

        bool success = await _service.DeleteAsync(id);
        Console.WriteLine(success ? $"✗ 已删除 #{id}" : $"未找到 #{id}");
    }

    private void HandleSearch(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("用法: search <关键词>");
            return;
        }

        string keyword = args[0];
        var results = _service.Search(keyword).ToList();

        Console.WriteLine($"\n🔍 搜索 \"{keyword}\" 结果:");
        if (!results.Any())
        {
            Console.WriteLine("  未找到匹配项");
            return;
        }

        foreach (var item in results)
            Console.WriteLine(item);
    }

    private void HandleStats()
    {
        var (total, completed, pending) = _service.GetStats();
        double completionRate = total > 0 ? (double)completed / total * 100 : 0;

        Console.WriteLine("\n📊 统计信息:");
        Console.WriteLine($"  总计:   {total}");
        Console.WriteLine($"  已完成: {completed}");
        Console.WriteLine($"  待完成: {pending}");
        Console.WriteLine($"  完成率: {completionRate:F1}%");

        // 按优先级统计
        foreach (Priority p in Enum.GetValues<Priority>())
        {
            int cnt = _service.GetAll().Count(t => t.Priority == p);
            if (cnt > 0) Console.WriteLine($"  {p}: {cnt} 项");
        }
    }

    private void PrintHelp()
    {
        Console.WriteLine("""
            Todo CLI — C# 命令行待办应用

            用法:
              todo add <标题> [--priority high|medium|low]   添加新任务
              todo list [all|pending|completed]               列出任务
              todo done <ID>                                  标记完成
              todo delete <ID>                                删除任务
              todo search <关键词>                            搜索任务
              todo stats                                      显示统计

            示例:
              todo add "学习 C# 泛型" --priority high
              todo add "读《CLR via C#》"
              todo list pending
              todo done 1
              todo search "C#"
            """);
    }
}

// ============================================================
//                      程序入口
// ============================================================

class Program
{
    static async Task Main(string[] args)
    {
        string dataFile = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
            ".todo.json"
        );

        var storage = new JsonFileStorage(dataFile);
        var service = new TodoService(storage);
        await service.InitializeAsync();

        // 演示模式:无参数时运行示例操作
        if (args.Length == 0)
        {
            await RunDemoAsync(service);
            return;
        }

        var cli = new TodoCli(service);
        await cli.RunAsync(args);
    }

    static async Task RunDemoAsync(TodoService service)
    {
        Console.WriteLine("=== Todo CLI 演示 ===\n");
        var cli = new TodoCli(service);

        // 添加任务
        await cli.RunAsync(new[] { "add", "学习 C# 基础语法", "--priority", "high" });
        await cli.RunAsync(new[] { "add", "完成 LINQ 练习", "--priority", "high" });
        await cli.RunAsync(new[] { "add", "阅读《C# 8.0 in a Nutshell》" });
        await cli.RunAsync(new[] { "add", "写一个 ASP.NET Core API", "--priority", "low" });
        await cli.RunAsync(new[] { "add", "配置开发环境" });

        Console.WriteLine();

        // 列出所有任务
        await cli.RunAsync(new[] { "list" });
        Console.WriteLine();

        // 完成几个任务
        await cli.RunAsync(new[] { "done", "1" });
        await cli.RunAsync(new[] { "done", "5" });
        Console.WriteLine();

        // 列出待完成任务
        await cli.RunAsync(new[] { "list", "pending" });
        Console.WriteLine();

        // 搜索
        await cli.RunAsync(new[] { "search", "C#" });
        Console.WriteLine();

        // 统计
        await cli.RunAsync(new[] { "stats" });
        Console.WriteLine();

        // 删除一个任务
        await cli.RunAsync(new[] { "delete", "4" });
        await cli.RunAsync(new[] { "list" });
    }
}

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布