实现IValidatableObject的Ticket类调用含yield的验证方法未执行问题
问题背景
我们有一个实现IValidatableObject接口的Ticket类,其Validate方法根据私有属性_action执行对象验证:
- 当
_action为ETicketAction.Create时,Assignee必须为null; - 当
_action为ETicketAction.Edit时,Assignee不可为null。
同时实现了私有方法ValidateCommonFields,用于验证ProductLineId、ProductVersion等公共字段,并在Create和Edit分支中调用。但调试发现,调用ValidateCommonFields()时从未进入方法内部,断点无响应,即使字段为空,该方法的ValidationResult也不会返回。怀疑和yield语句有关,想咨询是否该用yield return ValidateCommonFields()调用?
相关代码:
public partial class Ticket : IValidatableObject { public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (string.IsNullOrEmpty(Summary)) { yield return new ValidationResult("Summary must be set!"); } if (_action == (int)ETicketAction.Create) { if (Assignee != null) { yield return new ValidationResult("Assignee must be null when creating a ticket!"); } ValidateCommonFields(); } else if (_action == (int)ETicketAction.Edit) { if (Assignee == null) { yield return new ValidationResult("Assignee must be set when editing a ticket!"); } ValidateCommonFields(); } else if (_action == (int)ETicketAction.Close) { // ... } } private IEnumerable<ValidationResult> ValidateCommonFields() { if (ProductLineId == null) { yield return new ValidationResult("Product line must be set!"); } if (ProductVersion.IsNullOrEmpty()) { yield return new ValidationResult($"Product version must be set!"); } } }
原因
核心问题是带yield的迭代器方法是延迟执行的。当你只调用ValidateCommonFields()而不枚举它的返回值时,方法内部的代码不会运行——迭代器方法只有在被枚举(比如foreach遍历、调用ToList()/ToArray())时才会真正执行逻辑。
解决方案
不能直接用yield return ValidateCommonFields(),因为yield return只能返回单个元素,而该方法返回的是IEnumerable<ValidationResult>。有两种常用解决方式:
方式1:遍历公共验证结果并逐个返回
修改Validate方法中调用ValidateCommonFields的逻辑,用foreach遍历其返回值并逐个yield return:
// Create分支示例 if (_action == (int)ETicketAction.Create) { if (Assignee != null) { yield return new ValidationResult("Assignee must be null when creating a ticket!"); } // 遍历公共验证结果并返回 foreach (var result in ValidateCommonFields()) { yield return result; } } // Edit分支同理 else if (_action == (int)ETicketAction.Edit) { if (Assignee == null) { yield return new ValidationResult("Assignee must be set when editing a ticket!"); } foreach (var result in ValidateCommonFields()) { yield return result; } }
方式2:修改公共验证方法为添加结果到列表
调整ValidateCommonFields的逻辑,让它接收一个列表并添加验证结果,避免迭代器延迟执行:
private void ValidateCommonFields(List<ValidationResult> results) { if (ProductLineId == null) { results.Add(new ValidationResult("Product line must be set!")); } if (ProductVersion.IsNullOrEmpty()) { results.Add(new ValidationResult($"Product version must be set!")); } }
然后在Validate方法中创建列表,收集所有验证结果后返回:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { var results = new List<ValidationResult>(); if (string.IsNullOrEmpty(Summary)) { results.Add(new ValidationResult("Summary must be set!")); } if (_action == (int)ETicketAction.Create) { if (Assignee != null) { results.Add(new ValidationResult("Assignee must be null when creating a ticket!")); } ValidateCommonFields(results); } else if (_action == (int)ETicketAction.Edit) { if (Assignee == null) { results.Add(new ValidationResult("Assignee must be set when editing a ticket!")); } ValidateCommonFields(results); } else if (_action == (int)ETicketAction.Close) { // ... } foreach (var result in results) { yield return result; } }
这种方式逻辑更直观,也避免了迭代器延迟执行的坑。
内容的提问来源于stack exchange,提问作者Aleks Vujic

