如何在C#中实现Department与Employee的聚合关系并修复空引用错误
解决C#中Department与Employee聚合关系的空引用错误
问题核心
你要实现聚合关系(一名员工仅隶属于一个部门),但调用dept.GetIncentive时触发了「对象引用未设置为对象的实例」错误——本质是Employee的dept属性默认值为null,调用其方法时自然会抛出空引用异常。
错误原因
Employee类里的public Department dept { get; set; }是自动属性,C#中引用类型的默认值是null。如果创建Employee实例时没给dept赋值,后续调用dept.GetIncentive必然出错。
正确实现聚合关系+修复错误
聚合关系的核心逻辑是:员工依赖部门,但部门可以独立存在;员工必须关联一个有效的部门实例。下面提供两种可靠实现方案:
方案1:构造函数强制注入部门(推荐)
通过构造函数要求必须传入部门,从根源上避免dept为null,同时把dept设为只读属性,确保员工一旦归属某个部门就不能随意变更:
public class Employee { public int fixedSalary = 50000; public DateTime workingFrom = new DateTime(2012, 02, 27); // 只读属性,确保部门关联后不可随意修改 public Department dept { get; } // 构造函数强制传入部门,空值直接抛出异常 public Employee(Department department) { dept = department ?? throw new ArgumentNullException(nameof(department), "员工必须隶属于一个部门"); } // 原GetAllowance方法保持不变 public double GetAllowance() { return GetAllowance(new DateTime(2014, 03, 31)); } public double GetAllowance(DateTime cutOffDate) { double workExperience = ((cutOffDate - workingFrom).Days)/365; double allowance = 0; if (workExperience < 5) allowance = 0.05 * fixedSalary; else if(workExperience >= 5 && workExperience < 10) allowance = 0.1 * fixedSalary; else if(workExperience >= 10 && workExperience < 15) allowance = 0.15 * fixedSalary; else if (workExperience >= 15) allowance = 0.2 * fixedSalary; Console.WriteLine("work experience:{0}",workExperience); Console.WriteLine("allowance:{0}", allowance); return allowance; } // 原GetTotalSalary方法无需额外空值检查,因为dept必然有效 public double GetTotalSalary(DateTime cutOffDate, float multiplyFactor) { return fixedSalary + GetAllowance(cutOffDate) + dept.GetIncentive(multiplyFactor); } public double GetTotalSalary(float multiplyFactor) { return fixedSalary + GetAllowance() + dept.GetIncentive(multiplyFactor); } }
方案2:延迟关联+空值校验(适合需后期绑定部门的场景)
如果业务允许员工先创建、再关联部门,可通过私有字段封装dept,在赋值时做空值校验,同时在调用部门方法前加检查:
public class Employee { public int fixedSalary = 50000; public DateTime workingFrom = new DateTime(2012, 02, 27); private Department _dept; // 外部赋值时强制检查空值 public Department dept { get => _dept; set => _dept = value ?? throw new ArgumentNullException(nameof(value), "部门不能为null"); } // 无参构造函数,允许先创建员工 public Employee() { } // 修改GetTotalSalary方法,确保调用部门方法前已绑定部门 public double GetTotalSalary(DateTime cutOffDate, float multiplyFactor) { if (_dept == null) throw new InvalidOperationException("员工尚未关联任何部门"); return fixedSalary + GetAllowance(cutOffDate) + _dept.GetIncentive(multiplyFactor); } public double GetTotalSalary(float multiplyFactor) { if (_dept == null) throw new InvalidOperationException("员工尚未关联任何部门"); return fixedSalary + GetAllowance() + _dept.GetIncentive(multiplyFactor); } // GetAllowance方法保持不变... }
使用示例
不管用哪种方案,创建实例时都要确保部门实例有效:
// 先创建部门实例 var techDept = new Department { deptNumber = 1, isProducing = true, produce = 10000f }; // 方案1:构造函数注入 var emp1 = new Employee(techDept); Console.WriteLine(emp1.GetTotalSalary(0.1f)); // 方案2:后期绑定部门 var emp2 = new Employee(); emp2.dept = techDept; // 赋值null会直接抛出异常 Console.WriteLine(emp2.GetTotalSalary(0.1f));
额外优化建议
- 给Department类的字段加上封装(用属性代替公共字段):
public class Department { public int DeptNumber { get; set; } public bool IsProducing { get; set; } public float Produce { get; set; } public float GetIncentive(float multiplyFactor) { return IsProducing ? Produce * multiplyFactor : 0; } }
- 计算工作经验时用
(cutOffDate - workingFrom).TotalDays / 365.25,能更准确地考虑闰年因素。
内容的提问来源于stack exchange,提问作者Varun Gupta
相关产品推荐
相关产品推荐

