目 录CONTENT

文章目录

CSharp(三十四)学生管理系统(从零到完整项目)

CSharp(三十四)综合实战:学生管理系统(从零到完整项目)


目录

  1. 项目概述:我们要做什么
  2. 为什么需要综合练习
  3. 系统设计:类与类之间的关系
  4. 步骤1:定义枚举类型
  5. 步骤2:定义接口
  6. 步骤3:抽象基类 Person
  7. 步骤4:课程类 Course
  8. 步骤5:成绩类 Score
  9. 步骤6:学生类 Student(核心)
  10. 步骤7:教师类 Teacher
  11. 步骤8:班级类 ClassRoom
  12. 步骤9:报表工具类 ReportGenerator
  13. 步骤10:主程序 Main 串联所有
  14. 完整代码(可直接运行)
  15. 程序运行流程图
  16. 知识点速查表
  17. 扩展练习
  18. 小结

1. 项目概述:我们要做什么

一个真实场景

假设你是一个学校的教务系统开发人员。你需要写一个学生管理系统,功能包括:

教师 ──▶ 教授课程 ──▶ 给学生打成绩
                            │
                            ▼
学生 ──▶ 查看自己的成绩单 ──▶ 班级排名
                            │
                            ▼
                       班级报告生成

系统包含的角色

角色 能做什么
学生 自我介绍、学习、查看成绩单(平均分 + 每科成绩)
教师 自我介绍、负责多门课程、给学生录入成绩
课程 有编号、名称、学分、授课教师
班级 包含多名学生、有班主任、排名、全班平均分
报表 自动生成班级报告(排名、平均分等)

涉及的知识点

这个系统把前面学过的几乎所有 C# 类相关知识点串在一起

  • 枚举(Gender, Grade)
  • 接口(IIdentifiable, IStudy)
  • 抽象类(Person)
  • 继承(Student : Person, Teacher : Person)
  • 多态(virtual/override)
  • 属性(自动属性、只读属性、表达式体属性)
  • 静态成员(TotalStudents、ReportGenerator)
  • 事件(ScoreAdded)
  • 索引器(this[string courseCode])
  • 构造函数与 base 关键字
  • 集合(List<T>)
  • LINQ(Average, FirstOrDefault, OrderByDescending)

2. 为什么需要综合练习

学习编程就像学做菜

单个知识点         =  学会切菜、学会开火、学会放盐
综合练习           =  真正做出一道完整的菜

如果你只学了"切菜"就觉得自己会做菜,一到真正动手时就会发现——不知道先放油还是先放菜,不知道火候多大,不知道什么时候放盐。

这个综合案例就是让你把之前学的所有知识点串起来,做一道完整的"菜"。


3. 系统设计:类与类之间的关系

在写代码之前,先搞清楚谁和谁有关系:

                    IIdentifiable            IStudy
                         ▲                     ▲
                         │                     │
                   ┌─────┴─────┐               │
                   │  Person   │               │
                   │ (abstract)│               │
                   └─────┬─────┘               │
                         │                     │
              ┌──────────┼──────────┐          │
              ▼                     ▼          │
          Teacher               Student────────┘
              │                     │
              │ 授课                 │ 选修
              ▼                     ▼
          Course ◄──────────── Score(成绩)
              │
              │ 担任班主任
              ▼
          ClassRoom ─── 包含 ──▶ Student(多个)
              │
              ▼
         ReportGenerator(静态工具类)

关系解读

关系 代码体现 解释
继承 class Student : Person 学生"是一种"人
实现接口 class Student : IStudy 学生"具备"学习能力
组合(一对多) ClassRoom 里有 List<Student> 班级包含多个学生
关联 Teacher 里有 List<Course> 教师教授多门课
依赖 Score 里有 Course 成绩依赖于某门课

4. 步骤1:定义枚举类型

枚举(enum)用于定义一组有名字的常量,让代码更有可读性:

// 性别枚举
public enum Gender
{
    Male,    // 男(值=0)
    Female   // 女(值=1)
}

// 年级枚举
public enum Grade
{
    Freshman,   // 大一(值=0)
    Sophomore,  // 大二(值=1)
    Junior,     // 大三(值=2)
    Senior      // 大四(值=3)
}

为什么用枚举而不是直接用 int?

// ❌ 不好:谁看得懂 "0" 是什么意思?
Student s = new Student("张三", 20, 0, "2024001", 1, "计算机科学");
//                                               ↑男?↑大二? 不清晰!

// ✅ 好:一目了然
Student s = new Student("张三", 20, Gender.Male, "2024001", Grade.Sophomore, "计算机科学");
//                                ↑男                ↑大二

5. 步骤2:定义接口

接口定义"能做什么"的契约:

// 接口1:有身份标识
public interface IIdentifiable
{
    string Id { get; }  // 只读属性,所有实现者必须有 Id
}

// 接口2:学习能力
public interface IStudy
{
    void Study(string subject, int hours);   // 学习方法
    double GetAverageScore();                 // 获取平均分
}

接口的作用:当你看到 IStudy 接口,你就知道实现它的类一定能学习能计算平均分。你不关心具体是谁,只关心有这个能力。


6. 步骤3:抽象基类 Person

Person 是所有"人"的模板,提取学生和教师的公共部分:

public abstract class Person : IIdentifiable   // Person 实现了 IIdentifiable
{
    // ===== 只读属性:创建后不能改 =====
    public string Id { get; }

    // ===== 可读写属性 =====
    public string Name { get; set; }
    public int Age { get; set; }
    public Gender Gender { get; set; }

    // ===== 构造函数:必填项 =====
    public Person(string name, int age, Gender gender)
    {
        Id = Guid.NewGuid().ToString().Substring(0, 8);  // 自动生成8位ID
        Name = name;
        Age = age;
        Gender = gender;
    }

    // ===== 抽象方法:子类必须实现 =====
    // 每个人都要会自我介绍,但方式不同
    public abstract void Introduce();

    // ===== 虚方法:子类可选择重写 =====
    // 默认返回"人员",子类可以覆盖成"学生"或"教师"
    public virtual string GetRole()
    {
        return "人员";
    }
}

逐行讲解

代码 知识点 说明
public abstract class Person 抽象类 不能 new Person(),只能被继承
: IIdentifiable 接口实现 Person 承诺提供 Id
public string Id { get; } 只读属性 只允许在构造函数/初始化器中赋值
Guid.NewGuid().ToString().Substring(0, 8) 生成ID 生成全球唯一标识,取前8位
public abstract void Introduce() 抽象方法 没有方法体,子类必须重写
public virtual string GetRole() 虚方法 有默认实现,子类可选是否重写

7. 步骤4:课程类 Course

课程类用于描述一门课程的基本信息:

public class Course
{
    public string Code { get; }       // 课程编号,如 "CS101"
    public string Name { get; }       // 课程名称,如 "C#程序设计"
    public int Credits { get; }       // 学分
    public Teacher Teacher { get; set; }  // 授课教师

    // 构造函数:编号、名称、学分是必填项
    public Course(string code, string name, int credits)
    {
        Code = code;
        Name = name;
        Credits = credits;
    }

    // 方便打印
    public override string ToString() => $"[{Code}] {Name} ({Credits}学分)";
}

设计思路:编号/名称/学分是课程诞生就固定的(用只读属性),而授课教师是之后分配的(可读写属性)。


8. 步骤5:成绩类 Score

成绩类记录某学生某门课的分数:

public class Score
{
    public Course Course { get; }     // 哪门课
    public double Value { get; }     // 分数值
    public DateTime Date { get; }    // 录入时间

    public Score(Course course, double value)
    {
        Course = course;
        Value = value;
        Date = DateTime.Now;  // 自动记录录入时间
    }

    // 把数字分数转为 A/B/C/D/F 等级
    public string GetGrade()
    {
        if (Value >= 90) return "A";
        if (Value >= 80) return "B";
        if (Value >= 70) return "C";
        if (Value >= 60) return "D";
        return "F";
    }

    public override string ToString() => $"{Course.Name}: {Value}分 ({GetGrade()})";
}

为什么 Score 是一个独立的类?

你可以把 Score 做成这样:

// ❌ 不好的设计:把成绩塞进学生类
Dictionary<Course, double> scores;  // 存不了录入时间、等级等信息

但独立成一个类后,你可以随时扩展——比如增加"学期"、"是否补考"、"评语"等字段,而不用改数据结构。这就是面向对象"单一职责"原则的体现。


9. 步骤6:学生类 Student(核心)

这是整个系统最复杂的类,我们用分块讲解的方式逐个击破:

第一部分:类声明和属性

public class Student : Person, IStudy    // 继承 Person,实现 IStudy
{
    public string StudentNumber { get; }     // 学号(只读)
    public Grade Grade { get; set; }         // 年级
    public string Major { get; set; }        // 专业

    // 内部成绩列表(私有,外部通过方法访问)
    private List<Score> Scores { get; } = new List<Score>();

    // 静态字段:统计全校学生总数
    private static int totalStudents = 0;

    // 事件:有新成绩时触发通知
    public event EventHandler<Score> ScoreAdded;

知识点对照

代码 知识点
: Person, IStudy 多继承:一个类 + 多个接口
= new List<Score>() 属性初始化器(C# 6.0+)
private static int totalStudents 静态字段,属于类而非实例
event EventHandler<Score> 事件(发布/订阅模式)

第二部分:构造函数

    public Student(string name, int age, Gender gender,
                   string studentNumber, Grade grade, string major)
        : base(name, age, gender)   // ← 先调用父类 Person 的构造函数
    {
        StudentNumber = studentNumber;
        Grade = grade;
        Major = major;
        totalStudents++;   // 每创建一个学生,总数+1
    }

执行顺序

1. base(name, age, gender)  →  Person 的构造函数
2. StudentNumber = ...      →  自身字段赋值
3. totalStudents++          →  统计计数

第三部分:静态属性

    // 静态属性:外部通过 Student.TotalStudents 访问
    public static int TotalStudents => totalStudents;

第四部分:重写父类方法

    // 重写抽象方法(必须)
    public override void Introduce()
    {
        string genderStr = Gender == Gender.Male ? "男" : "女";
        Console.WriteLine($"我叫{Name},{Age}岁,{genderStr}," +
                          $"学号{StudentNumber},{Major}专业大{(int)Grade + 1}学生");
    }

    // 重写虚方法(可选)
    public override string GetRole() => "学生";

第五部分:实现接口方法

    // 实现 IStudy 接口
    public void Study(string subject, int hours)
    {
        Console.WriteLine($"{Name} 学习了 {subject} {hours} 小时");
    }

    public double GetAverageScore()
    {
        if (Scores.Count == 0) return 0;
        return Scores.Average(s => s.Value);  // LINQ 求平均
    }

第六部分:核心业务方法

    // 添加成绩(带验证)
    public void AddScore(Course course, double value)
    {
        if (value < 0 || value > 100)
            throw new ArgumentException("成绩必须在0-100之间");

        Score score = new Score(course, value);
        Scores.Add(score);
        ScoreAdded?.Invoke(this, score);  // 触发事件(通知订阅者)
    }

ScoreAdded?.Invoke(this, score) 解读:

  • ?. = 如果没人订阅事件,就不触发(避免空引用异常)
  • this = 事件源(谁触发的)
  • score = 事件数据(传递成绩信息)

第七部分:索引器

    // 索引器:按课程代码查成绩
    // 用法:student["CS101"] → 返回该课程分数
    public double? this[string courseCode]
    {
        get
        {
            var score = Scores.FirstOrDefault(s => s.Course.Code == courseCode);
            return score?.Value;  // 找到返回分数,没找到返回 null
        }
    }

double? 中的 ? 表示可空类型,没找到时返回 null

第八部分:打印成绩单

    public void PrintTranscript()
    {
        Console.WriteLine($"\n===== {Name} 的成绩单 =====");
        Console.WriteLine($"学号:{StudentNumber}  专业:{Major}");
        Console.WriteLine($"平均分:{GetAverageScore():F1}");
        Console.WriteLine("科目成绩:");

        foreach (var score in Scores)
        {
            Console.WriteLine($"  {score}");
        }

        if (Scores.Count == 0)
        {
            Console.WriteLine("  暂无成绩");
        }
        Console.WriteLine("=============================\n");
    }
}

10. 步骤7:教师类 Teacher

教师类比学生类简单,但也继承自 Person:

public class Teacher : Person
{
    public string Department { get; set; }  // 所在院系
    public string Title { get; set; }        // 职称(教授/副教授/讲师)
    private List<Course> courses = new List<Course>();  // 负责的课程

    public Teacher(string name, int age, Gender gender,
                   string department, string title)
        : base(name, age, gender)
    {
        Department = department;
        Title = title;
    }

    // 重写抽象方法
    public override void Introduce()
    {
        Console.WriteLine($"我是{Title}{Name},在{Department}系任教");
    }

    public override string GetRole() => "教师";

    // 分配课程
    public void AssignCourse(Course course)
    {
        courses.Add(course);
        course.Teacher = this;  // 双向关联:告诉课程"你被我教"
    }

    // 教师录入学生成绩(核心业务)
    public void GradeStudent(Student student, Course course, double score)
    {
        Console.WriteLine($"{Name} 给 {student.Name} 在 {course.Name} 录入成绩:{score}");
        student.AddScore(course, score);
    }
}

11. 步骤8:班级类 ClassRoom

班级管理一组学生:

public class ClassRoom
{
    public string Name { get; }               // 班级名称
    public Teacher HeadTeacher { get; set; }  // 班主任
    private List<Student> students = new List<Student>();

    public int StudentCount => students.Count;  // 表达式体属性

    public ClassRoom(string name)
    {
        Name = name;
    }

    // ===== 索引器:按位置访问学生 =====
    public Student this[int index] => students[index];

    // ===== 添加学生 =====
    public void AddStudent(Student student)
    {
        students.Add(student);
        Console.WriteLine($"{student.Name} 加入班级 {Name}");
    }

    // ===== 按学号查找 =====
    public Student FindByStudentNumber(string number)
    {
        return students.FirstOrDefault(s => s.StudentNumber == number);
    }

    // ===== 打印全班名单 =====
    public void PrintAllStudents()
    {
        Console.WriteLine($"\n===== 班级 {Name} 学生名单(共{StudentCount}人)=====");
        foreach (var s in students)
        {
            s.Introduce();  // ← 多态调用!
        }
        Console.WriteLine("============================================\n");
    }

    // ===== 班级平均分 =====
    public double GetClassAverageScore()
    {
        if (students.Count == 0) return 0;
        return students.Average(s => s.GetAverageScore());
    }

    // ===== 班级排名(按平均分降序)=====
    public List<Student> GetRanking()
    {
        return students.OrderByDescending(s => s.GetAverageScore()).ToList();
    }
}

亮点PrintAllStudents() 中调用 s.Introduce() 时,虽然类型是 Student,但如果将来有 ExchangeStudent(交换生)继承 Student 并重写了 Introduce,这个循环不需要改任何代码就能正确调用。这就是多态的威力。


12. 步骤9:报表工具类 ReportGenerator

这是一个静态类,专注于生成班级报告:

public static class ReportGenerator
{
    public static void GenerateClassReport(ClassRoom classroom)
    {
        Console.WriteLine(new string('=', 50));
        Console.WriteLine($"         {classroom.Name} 班级报告");
        Console.WriteLine(new string('=', 50));
        Console.WriteLine($"班主任:{classroom.HeadTeacher?.Name ?? "未指定"}");
        Console.WriteLine($"学生人数:{classroom.StudentCount}");
        Console.WriteLine($"班级平均分:{classroom.GetClassAverageScore():F1}");
        Console.WriteLine("\n排名:");

        var ranking = classroom.GetRanking();
        for (int i = 0; i < ranking.Count; i++)
        {
            Console.WriteLine($"  {i + 1}. {ranking[i].Name} - 平均分:{ranking[i].GetAverageScore():F1}");
        }
        Console.WriteLine(new string('=', 50));
    }
}

为什么用静态类? 报表生成不需要保存状态,用的时候就调一下,不需要创建 new ReportGenerator()


13. 步骤10:主程序 Main 串联所有

这一步就是把所有零件组装成完整系统

13.1 创建课程

Course math = new Course("MATH101", "高等数学", 5);
Course english = new Course("ENG101", "大学英语", 4);
Course programming = new Course("CS101", "C#程序设计", 4);

13.2 创建教师并分配课程

Teacher teacher1 = new Teacher("王老师", 38, Gender.Male, "计算机科学", "教授");
Teacher teacher2 = new Teacher("陈老师", 42, Gender.Female, "数学", "副教授");

teacher1.Introduce();  // 输出:我是教授王老师,在计算机科学系任教
teacher2.Introduce();  // 输出:我是副教授陈老师,在数学系任教

// 一个教师可以教多门课
teacher1.AssignCourse(programming);
teacher2.AssignCourse(math);
teacher1.AssignCourse(english);

13.3 创建学生

Student s1 = new Student("张三", 20, Gender.Male, "2024001", Grade.Sophomore, "计算机科学");
Student s2 = new Student("李四", 21, Gender.Female, "2024002", Grade.Sophomore, "计算机科学");
Student s3 = new Student("王五", 19, Gender.Male, "2024003", Grade.Freshman, "软件工程");
Student s4 = new Student("赵六", 22, Gender.Female, "2024004", Grade.Junior, "计算机科学");

s1.Introduce();  // 输出:我叫张三,20岁,男,学号2024001,计算机科学专业大二学生
s2.Introduce();

13.4 教师录成绩

teacher1.GradeStudent(s1, programming, 92);  // 王老师给张三录编程成绩92
teacher1.GradeStudent(s1, english, 85);
teacher2.GradeStudent(s1, math, 88);

teacher1.GradeStudent(s2, programming, 78);
teacher1.GradeStudent(s2, english, 90);
teacher2.GradeStudent(s2, math, 65);

teacher1.GradeStudent(s3, programming, 95);

teacher1.GradeStudent(s4, programming, 82);
teacher1.GradeStudent(s4, english, 79);
teacher2.GradeStudent(s4, math, 91);

13.5 组建班级

ClassRoom class1 = new ClassRoom("计科2024-1班");
class1.HeadTeacher = teacher1;  // 王老师当班主任
class1.AddStudent(s1);
class1.AddStudent(s2);
class1.AddStudent(s3);
class1.AddStudent(s4);

13.6 打印成绩单

Console.WriteLine($"\n当前共有 {Student.TotalStudents} 名学生");  // 输出:4
s1.PrintTranscript();
s4.PrintTranscript();

13.7 使用索引器查成绩

Console.WriteLine($"s1 的编程成绩:{s1["CS101"]}");  // 输出:92

13.8 生成班级报告

class1.PrintAllStudents();
ReportGenerator.GenerateClassReport(class1);

13.9 多态演示

Console.WriteLine("\n===== 多态演示 =====");
List<Person> people = new List<Person> { s1, s2, teacher1, teacher2 };
foreach (Person p in people)     // p 的类型是 Person
{
    Console.Write($"身份:{p.GetRole()},");
    p.Introduce();  // 但实际调用的是 Student 或 Teacher 的版本!
}
// 输出:
//   身份:学生,我叫张三,20岁,男,学号2024001,计算机科学专业大二学生
//   身份:学生,我叫李四,21岁,女,学号2024002,计算机科学专业大二学生
//   身份:教师,我是教授王老师,在计算机科学系任教
//   身份:教师,我是副教授陈老师,在数学系任教

这就是多态:用统一的 Person 类型引用,但每个对象按照自己的方式执行 Introduce()


14. 完整代码(可直接运行)

using System;
using System.Collections.Generic;
using System.Linq;

// ========== 枚举 ==========
public enum Gender
{
    Male, Female
}

public enum Grade
{
    Freshman, Sophomore, Junior, Senior
}

// ========== 接口 ==========
public interface IIdentifiable
{
    string Id { get; }
}

public interface IStudy
{
    void Study(string subject, int hours);
    double GetAverageScore();
}

// ========== 抽象基类 ==========
public abstract class Person : IIdentifiable
{
    public string Id { get; }
    public string Name { get; set; }
    public int Age { get; set; }
    public Gender Gender { get; set; }

    public Person(string name, int age, Gender gender)
    {
        Id = Guid.NewGuid().ToString().Substring(0, 8);
        Name = name;
        Age = age;
        Gender = gender;
    }

    public abstract void Introduce();

    public virtual string GetRole()
    {
        return "人员";
    }
}

// ========== 课程类 ==========
public class Course
{
    public string Code { get; }
    public string Name { get; }
    public int Credits { get; }
    public Teacher Teacher { get; set; }

    public Course(string code, string name, int credits)
    {
        Code = code;
        Name = name;
        Credits = credits;
    }

    public override string ToString() => $"[{Code}] {Name} ({Credits}学分)";
}

// ========== 成绩记录 ==========
public class Score
{
    public Course Course { get; }
    public double Value { get; }
    public DateTime Date { get; }

    public Score(Course course, double value)
    {
        Course = course;
        Value = value;
        Date = DateTime.Now;
    }

    public string GetGrade()
    {
        if (Value >= 90) return "A";
        if (Value >= 80) return "B";
        if (Value >= 70) return "C";
        if (Value >= 60) return "D";
        return "F";
    }

    public override string ToString() => $"{Course.Name}: {Value}分 ({GetGrade()})";
}

// ========== 学生类 ==========
public class Student : Person, IStudy
{
    public string StudentNumber { get; }
    public Grade Grade { get; set; }
    public string Major { get; set; }
    private List<Score> Scores { get; } = new List<Score>();

    private static int totalStudents = 0;

    public event EventHandler<Score> ScoreAdded;

    public Student(string name, int age, Gender gender,
                   string studentNumber, Grade grade, string major)
        : base(name, age, gender)
    {
        StudentNumber = studentNumber;
        Grade = grade;
        Major = major;
        totalStudents++;
    }

    public static int TotalStudents => totalStudents;

    public override void Introduce()
    {
        string genderStr = Gender == Gender.Male ? "男" : "女";
        Console.WriteLine($"我叫{Name},{Age}岁,{genderStr}," +
                          $"学号{StudentNumber},{Major}专业大{(int)Grade + 1}学生");
    }

    public override string GetRole() => "学生";

    public void Study(string subject, int hours)
    {
        Console.WriteLine($"{Name} 学习了 {subject} {hours} 小时");
    }

    public void AddScore(Course course, double value)
    {
        if (value < 0 || value > 100)
            throw new ArgumentException("成绩必须在0-100之间");

        Score score = new Score(course, value);
        Scores.Add(score);
        ScoreAdded?.Invoke(this, score);
    }

    public double GetAverageScore()
    {
        if (Scores.Count == 0) return 0;
        return Scores.Average(s => s.Value);
    }

    public double? this[string courseCode]
    {
        get
        {
            var score = Scores.FirstOrDefault(s => s.Course.Code == courseCode);
            return score?.Value;
        }
    }

    public void PrintTranscript()
    {
        Console.WriteLine($"\n===== {Name} 的成绩单 =====");
        Console.WriteLine($"学号:{StudentNumber}  专业:{Major}");
        Console.WriteLine($"平均分:{GetAverageScore():F1}");
        Console.WriteLine("科目成绩:");

        foreach (var score in Scores)
        {
            Console.WriteLine($"  {score}");
        }

        if (Scores.Count == 0)
        {
            Console.WriteLine("  暂无成绩");
        }
        Console.WriteLine("=============================\n");
    }
}

// ========== 教师类 ==========
public class Teacher : Person
{
    public string Department { get; set; }
    public string Title { get; set; }
    private List<Course> courses = new List<Course>();

    public Teacher(string name, int age, Gender gender,
                   string department, string title)
        : base(name, age, gender)
    {
        Department = department;
        Title = title;
    }

    public override void Introduce()
    {
        Console.WriteLine($"我是{Title}{Name},在{Department}系任教");
    }

    public override string GetRole() => "教师";

    public void AssignCourse(Course course)
    {
        courses.Add(course);
        course.Teacher = this;
    }

    public void GradeStudent(Student student, Course course, double score)
    {
        Console.WriteLine($"{Name} 给 {student.Name} 在 {course.Name} 录入成绩:{score}");
        student.AddScore(course, score);
    }
}

// ========== 班级类 ==========
public class ClassRoom
{
    public string Name { get; }
    public Teacher HeadTeacher { get; set; }
    private List<Student> students = new List<Student>();

    public int StudentCount => students.Count;

    public ClassRoom(string name)
    {
        Name = name;
    }

    public Student this[int index] => students[index];

    public void AddStudent(Student student)
    {
        students.Add(student);
        Console.WriteLine($"{student.Name} 加入班级 {Name}");
    }

    public Student FindByStudentNumber(string number)
    {
        return students.FirstOrDefault(s => s.StudentNumber == number);
    }

    public void PrintAllStudents()
    {
        Console.WriteLine($"\n===== 班级 {Name} 学生名单(共{StudentCount}人)=====");
        foreach (var s in students)
        {
            s.Introduce();
        }
        Console.WriteLine("============================================\n");
    }

    public double GetClassAverageScore()
    {
        if (students.Count == 0) return 0;
        return students.Average(s => s.GetAverageScore());
    }

    public List<Student> GetRanking()
    {
        return students.OrderByDescending(s => s.GetAverageScore()).ToList();
    }
}

// ========== 静态工具类 ==========
public static class ReportGenerator
{
    public static void GenerateClassReport(ClassRoom classroom)
    {
        Console.WriteLine(new string('=', 50));
        Console.WriteLine($"         {classroom.Name} 班级报告");
        Console.WriteLine(new string('=', 50));
        Console.WriteLine($"班主任:{classroom.HeadTeacher?.Name ?? "未指定"}");
        Console.WriteLine($"学生人数:{classroom.StudentCount}");
        Console.WriteLine($"班级平均分:{classroom.GetClassAverageScore():F1}");
        Console.WriteLine("\n排名:");

        var ranking = classroom.GetRanking();
        for (int i = 0; i < ranking.Count; i++)
        {
            Console.WriteLine($"  {i + 1}. {ranking[i].Name} - 平均分:{ranking[i].GetAverageScore():F1}");
        }
        Console.WriteLine(new string('=', 50));
    }
}

// ========== 主程序 ==========
class Program
{
    static void Main(string[] args)
    {
        // 1. 创建课程
        Course math = new Course("MATH101", "高等数学", 5);
        Course english = new Course("ENG101", "大学英语", 4);
        Course programming = new Course("CS101", "C#程序设计", 4);

        // 2. 创建教师
        Teacher teacher1 = new Teacher("王老师", 38, Gender.Male, "计算机科学", "教授");
        Teacher teacher2 = new Teacher("陈老师", 42, Gender.Female, "数学", "副教授");

        teacher1.Introduce();
        teacher2.Introduce();

        // 分配课程给教师
        teacher1.AssignCourse(programming);
        teacher2.AssignCourse(math);
        teacher1.AssignCourse(english);

        // 3. 创建学生
        Student s1 = new Student("张三", 20, Gender.Male, "2024001", Grade.Sophomore, "计算机科学");
        Student s2 = new Student("李四", 21, Gender.Female, "2024002", Grade.Sophomore, "计算机科学");
        Student s3 = new Student("王五", 19, Gender.Male, "2024003", Grade.Freshman, "软件工程");
        Student s4 = new Student("赵六", 22, Gender.Female, "2024004", Grade.Junior, "计算机科学");

        // 4. 学生自我介绍
        s1.Introduce();
        s2.Introduce();

        // 5. 教师录成绩
        teacher1.GradeStudent(s1, programming, 92);
        teacher1.GradeStudent(s1, english, 85);
        teacher2.GradeStudent(s1, math, 88);

        teacher1.GradeStudent(s2, programming, 78);
        teacher1.GradeStudent(s2, english, 90);
        teacher2.GradeStudent(s2, math, 65);

        teacher1.GradeStudent(s3, programming, 95);

        teacher1.GradeStudent(s4, programming, 82);
        teacher1.GradeStudent(s4, english, 79);
        teacher2.GradeStudent(s4, math, 91);

        // 6. 创建班级
        ClassRoom class1 = new ClassRoom("计科2024-1班");
        class1.HeadTeacher = teacher1;
        class1.AddStudent(s1);
        class1.AddStudent(s2);
        class1.AddStudent(s3);
        class1.AddStudent(s4);

        // 7. 打印成绩单
        Console.WriteLine($"\n当前共有 {Student.TotalStudents} 名学生");
        s1.PrintTranscript();
        s4.PrintTranscript();

        // 8. 使用索引器查成绩
        Console.WriteLine($"s1 的编程成绩:{s1["CS101"]}");

        // 9. 班级报告
        class1.PrintAllStudents();
        ReportGenerator.GenerateClassReport(class1);

        // 10. 多态演示
        Console.WriteLine("\n===== 多态演示 =====");
        List<Person> people = new List<Person> { s1, s2, teacher1, teacher2 };
        foreach (Person p in people)
        {
            Console.Write($"身份:{p.GetRole()},");
            p.Introduce();
        }
    }
}

运行输出预览

我是教授王老师,在计算机科学系任教
我是副教授陈老师,在数学系任教
我叫张三,20岁,男,学号2024001,计算机科学专业大二学生
我叫李四,21岁,女,学号2024002,计算机科学专业大二学生
王老师 给 张三 在 C#程序设计 录入成绩:92
王老师 给 张三 在 大学英语 录入成绩:85
陈老师 给 张三 在 高等数学 录入成绩:88
...

当前共有 4 名学生

===== 张三 的成绩单 =====
学号:2024001  专业:计算机科学
平均分:88.3
科目成绩:
  C#程序设计: 92分 (A)
  大学英语: 85分 (B)
  高等数学: 88分 (B)
=============================

s1 的编程成绩:92

===== 班级 计科2024-1班 学生名单(共4人)=====
我叫张三,20岁,男,学号2024001,计算机科学专业大二学生
我叫李四,21岁,女,学号2024002,计算机科学专业大二学生
我叫王五,19岁,男,学号2024003,软件工程专业大一学生
我叫赵六,22岁,女,学号2024004,计算机科学专业大三学生
============================================

==================================================
         计科2024-1班 班级报告
==================================================
班主任:王老师
学生人数:4
班级平均分:82.5

排名:
  1. 张三 - 平均分:88.3
  2. 赵六 - 平均分:84.0
  3. 李四 - 平均分:77.7
  4. 王五 - 平均分:95.0    ← 注意:王五只考了一门课
==================================================

===== 多态演示 =====
身份:学生,我叫张三,20岁,男,学号2024001,计算机科学专业大二学生
身份:学生,我叫李四,21岁,女,学号2024002,计算机科学专业大二学生
身份:教师,我是教授王老师,在计算机科学系任教
身份:教师,我是副教授陈老师,在数学系任教

15. 程序运行流程图

Main() 开始
    │
    ├── 创建3门课程 (math, english, programming)
    │
    ├── 创建2位教师 (teacher1, teacher2)
    │       └── 分配课程给教师
    │
    ├── 创建4名学生 (s1, s2, s3, s4)
    │       └── totalStudents 自动变为 4
    │
    ├── 录入成绩
    │   ├── teacher1.GradeStudent(s1, programming, 92)
    │   │       └── s1.AddScore(programming, 92)
    │   │               └── 触发 ScoreAdded 事件
    │   ├── ... (多条成绩录入)
    │
    ├── 创建班级 (class1)
    │       └── 添加4名学生,设班主任
    │
    ├── 打印成绩单
    │       └── 每科成绩 + 平均分 + 等级
    │
    ├── 索引器查询 s1["CS101"] → 92
    │
    ├── 班级报告
    │       └── 排名 → s1第1, s2第2, ...
    │
    └── 多态演示
            └── Person类型的List调用各自的Introduce()

16. 知识点速查表

知识点 在本项目中的位置 关键代码
枚举 Gender、Grade public enum Gender { Male, Female }
接口 IIdentifiable、IStudy public interface IStudy { ... }
抽象类 Person public abstract class Person
抽象方法 Person.Introduce() public abstract void Introduce();
虚方法 Person.GetRole() public virtual string GetRole()
继承 Student : Person、Teacher : Person class Student : Person, IStudy
多态 Main 的 foreach (Person p in people) p.Introduce() 调用子类版本
base 关键字 Student 构造函数 : base(name, age, gender)
属性 Person.Name、Course.Code public string Name { get; set; }
只读属性 Person.Id、Course.Code public string Id { get; }
静态成员 Student.totalStudents、ReportGenerator private static int totalStudents
事件 Student.ScoreAdded public event EventHandler<Score> ScoreAdded
索引器 Student.this[string]、ClassRoom.this[int] public double? this[string courseCode]
构造函数 每个类都有 public Person(string name, ...)
LINQ GetAverageScore、GetRanking Scores.Average(s => s.Value)
静态类 ReportGenerator public static class ReportGenerator
对象初始化器 可以用于创建课程的 List(可选) new Course { Code="CS101", ... }

17. 扩展练习

基于这个系统,你可以做以下练习:

练习1:增加学期概念

给 Score 类增加 Semester 属性(如 "2026春"),修改成绩单显示按学期分组。

练习2:成绩排名优化

当前排名只按平均分。但如果两个学生平均分相同,应该按获得 A 的数量再排一次。请实现这个逻辑。

练习3:增加课程选修系统

给 Student 类增加一个 Enroll(Course course) 方法,让学生自己选课。只有选了的课才能录入成绩。

练习4:补考机制

如果一个学生的某门课成绩低于 60 分(F 等级),自动标记为"需补考",并添加一个 MakeupScore 属性记录补考成绩。

练习5:文件读写

System.IO.File 把班级报告保存为文本文件,文件名用 {班级名称}_报告_{日期}.txt 格式。


18. 小结

┌────────────────────────────────────────────────────────────┐
│                  综合案例核心要点                            │
├────────────────────────────────────────────────────────────┤
│  设计原则:  先分析需求, 再画类关系图, 最后写代码              │
│                                                            │
│  分层结构:  枚举 → 接口 → 抽象类 → 具体类 → 工具类 → Main    │
│                                                            │
│  继承关系:  Person(抽象) → Student / Teacher                │
│                                                            │
│  接口实现:  IIdentifiable → Person                         │
│            IStudy → Student                                │
│                                                            │
│  多态体现:  List<Person> 中调用 Introduce()                 │
│            每个对象按自己的方式执行                          │
│                                                            │
│  数据关系:  Teacher ──教──▶ Course ◀──成绩── Score          │
│            ClassRoom ──包含──▶ Student(多个)               │
│                                                            │
│  静态工具:  ReportGenerator 不创建实例, 直接调用方法          │
│                                                            │
│  关键技巧:  base() 调父类构造 / 索引器 / 事件 / LINQ        │
└────────────────────────────────────────────────────────────┘

一句话总结

这个学生管理系统综合运用了 C# 类的几乎所有核心特性——从枚举到多态,从事件到索引器。先理解每个零件的作用,再观察它们如何协同工作。建议先把完整代码复制到 VS 里跑一遍,然后尝试做扩展练习,这是最快的进步方式。


0
博主关闭了当前页面的评论