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

现有C#控制台应用代码的最佳组织结构方案咨询

C#控制台项目合理组织结构与规范实现

把代码拆分到不同文件、按职责划分模块是非常好的实践,能大幅提升代码的可读性、可维护性和可测试性。针对你的阿森纳球员统计项目,这里给出一套清晰的重构方案:

一、核心拆分原则:单一职责

每个类/文件只负责一件事,避免把模型、数据读取、业务逻辑、UI交互混在一起。

二、重构后的项目结构

ArsenalPlayerTracker/
├── Models/                # 数据模型层:存放实体类
│   └── Player.cs
├── Data/                  # 数据访问层:负责数据加载/持久化
│   └── PlayerDataReader.cs
├── Services/              # 业务逻辑层:处理统计、筛选等核心逻辑
│   └── PlayerStatisticsService.cs
├── Program.cs             # UI层:控制台交互与输出
└── appsettings.json       # 配置文件:存放CSV路径等可配置项

三、各模块代码实现示例

1. Models/Player.cs

纯粹作为数据载体,不包含业务逻辑或数据解析代码:

namespace ArsenalPlayerTracker.Models
{
    public class Player
    {
        public string PlayerId { get; set; }
        public string Name { get; set; }
        public int BirthYear { get; set; }
        public string Nationality { get; set; }
        public string Position { get; set; }
        public string ClubId { get; set; }
        public string ClubName { get; set; }
        public int ShirtNumber { get; set; }
        public int Rating { get; set; }
        public string StrongFoot { get; set; }
        public int Height { get; set; }
        public DateTime ContractStart { get; set; }
        public DateTime ContractEnd { get; set; }
        public int FormRating { get; set; }
    }
}

2. Data/PlayerDataReader.cs

专门负责从CSV加载并解析球员数据,处理可能的格式异常:

using System.Collections.Generic;
using System.IO;
using System.Linq;
using ArsenalPlayerTracker.Models;

namespace ArsenalPlayerTracker.Data
{
    public class PlayerDataReader
    {
        private readonly string _csvFilePath;

        public PlayerDataReader(string csvFilePath)
        {
            _csvFilePath = csvFilePath;
        }

        public IEnumerable<Player> LoadPlayers()
        {
            var players = new List<Player>();
            var lines = File.ReadAllLines(_csvFilePath).Skip(1); // 跳过表头

            foreach (var line in lines)
            {
                var data = line.Split(';');
                if (data.Length != 14) continue; // 跳过格式错误的行

                if (int.TryParse(data[2], out int birthYear) &&
                    int.TryParse(data[7], out int shirtNumber) &&
                    int.TryParse(data[8], out int rating) &&
                    int.TryParse(data[10], out int height) &&
                    DateTime.TryParse(data[11], out DateTime contractStart) &&
                    DateTime.TryParse(data[12], out DateTime contractEnd) &&
                    int.TryParse(data[13], out int formRating))
                {
                    players.Add(new Player
                    {
                        PlayerId = data[0],
                        Name = data[1],
                        BirthYear = birthYear,
                        Nationality = data[3],
                        Position = data[4],
                        ClubId = data[5],
                        ClubName = data[6],
                        ShirtNumber = shirtNumber,
                        Rating = rating,
                        StrongFoot = data[9],
                        Height = height,
                        ContractStart = contractStart,
                        ContractEnd = contractEnd,
                        FormRating = formRating
                    });
                }
            }

            return players;
        }
    }
}

3. Services/PlayerStatisticsService.cs

封装所有统计、筛选逻辑,依赖抽象而非具体数据:

using System.Collections.Generic;
using System.Linq;
using ArsenalPlayerTracker.Models;

namespace ArsenalPlayerTracker.Services
{
    public class PlayerStatisticsService
    {
        private readonly IEnumerable<Player> _players;

        public PlayerStatisticsService(IEnumerable<Player> players)
        {
            _players = players;
        }

        public int GetTotalPlayerCount() => _players.Count();

        public IEnumerable<Player> GetForwards() => _players.Where(p => p.Position == "Forward");

        public IEnumerable<Player> GetPlayersByNationality(string nationality) =>
            _players.Where(p => p.Nationality.Equals(nationality, System.StringComparison.OrdinalIgnoreCase));

        public double GetAverageRating() => _players.Any() ? _players.Average(p => p.Rating) : 0;

        public Player GetTopRatedPlayer() => _players.OrderByDescending(p => p.Rating).FirstOrDefault();

        public double GetAverageRatingForClub(string clubName)
        {
            var clubPlayers = _players.Where(p => p.ClubName.Equals(clubName, System.StringComparison.OrdinalIgnoreCase));
            return clubPlayers.Any() ? clubPlayers.Average(p => p.Rating) : 0;
        }
    }
}

4. Program.cs

只负责控制台交互、调用各层服务,逻辑简洁:

using System;
using ArsenalPlayerTracker.Data;
using ArsenalPlayerTracker.Services;
using Microsoft.Extensions.Configuration;

namespace ArsenalPlayerTracker
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 加载配置
            var config = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json", optional: false)
                .Build();
            var csvPath = config["CsvFilePath"];

            // 初始化各层服务
            var dataReader = new PlayerDataReader(csvPath);
            var players = dataReader.LoadPlayers();
            var statsService = new PlayerStatisticsService(players);

            // 执行统计任务
            Console.WriteLine($"Task 2: Number of players: {statsService.GetTotalPlayerCount()}");

            Console.WriteLine("\nTask 3: Forwards:");
            foreach (var p in statsService.GetForwards())
            {
                Console.WriteLine($"\t{p.Name} - {p.Rating}");
            }

            Console.Write("\nTask 4: Nationality: ");
            string nat = Console.ReadLine();
            var natPlayers = statsService.GetPlayersByNationality(nat);
            if (!natPlayers.Any())
            {
                Console.WriteLine("\tNo players found with this nationality.");
            }
            else
            {
                foreach (var p in natPlayers)
                {
                    Console.WriteLine($"\t{p.Name} - {p.ClubName}");
                }
            }

            Console.WriteLine($"\nTask 5: Average rating: {statsService.GetAverageRating():F2}");

            var topPlayer = statsService.GetTopRatedPlayer();
            if (topPlayer != null)
            {
                Console.WriteLine($"Task 6: Best player: {topPlayer.Name} ({topPlayer.Rating})");
            }

            Console.WriteLine($"Task 7: Arsenal average rating: {statsService.GetAverageRatingForClub("Arsenal"):F2}");

            Console.ReadKey();
        }
    }
}

5. appsettings.json

存放可配置参数,避免硬编码:

{
  "CsvFilePath": "arsenal_players.csv"
}

四、额外规范建议

  • 命名空间:用项目名+模块名的结构(如ArsenalPlayerTracker.Models),避免默认的ConsoleApp1
  • 异常处理:在数据读取层加入TryParse而非直接Parse,防止程序因CSV格式错误崩溃
  • 依赖注入:如果项目规模扩大,可以引入依赖注入框架(如Microsoft.Extensions.DependencyInjection)来管理服务实例
  • 测试友好:业务逻辑层依赖IEnumerable<Player>,方便编写单元测试时传入模拟数据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 05:37:28