使用 LINQ 的条件获取

Conditional take with LINQ

我有几个验证器正在验证一个 IDeliveryObject,它在概念上可以描述为一个包含多行的文件。那部分工作正常。

IEnumerable<IDeliveryValidator> _validators; // Populated in ctor. Usually around 20 different validators.

private IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
    var validationErrors = new List<IValidationResult>();
    int maxNumberOfErrors = 10;
    foreach (IDeliveryValidator deliveryValidator in _validators)
    {
        IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject).Take(maxNumberOfErrors);
        validationErrors.AddRange(results);
        if (validationErrors.Count >= maxNumberOfErrors )
        {
            return validationErrors.Take(maxNumberOfErrors).ToList();
        }
    }
    return validationErrors;
}

逻辑遍历几个验证器,它们都针对不同的事情验证文件。

验证器可以看起来像这样:

public IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
    using (var reader = File.OpenText(deliveryObject.FilePath))
    {
        int expectedLength = 10; // Or some other value.
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            var lineLength = line.Length;
            if (lineLength != expectedLength)
            {
                // yield an error for each incorrect row.
                yield return new DeliveryValidationResult("Wrong length...");
            }
        }
    }
}

ValidationResult 看起来像这样:

public class DeliveryValidationResult : ValidationResult, IValidationResult
{

    public DeliveryValidationResult(bool isSoftError, string errorMessage) : base(errorMessage)
    {
        IsSoftError = isSoftError;
    }

    public DeliveryValidationResult(string errorMessage) : base(errorMessage)
    {
    }

    public DeliveryValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames)
    {
    }

    public DeliveryValidationResult(ValidationResult validationResult) : base(validationResult)
    {
    }

    public bool IsSoftError { get; set; }
}

public interface IValidationResult
{
    string ErrorMessage { get; set; }
    bool IsSoftError { get; set; }
}

感谢 Take(maxNumberOfErrors)yield 每个验证器只会 return 10 个验证结果,这在过去是很好的。但是现在我需要处理"soft validation result",这是同一种验证结果,但它不应该包含在产生的结果数中。这是一种警告,通过在IValidationResult中设置IsSoftError来定义。验证器可以产生 "soft validation result" 和 "regular validation result".

我要的是取x个验证结果+无限个软验证结果,这样所有IValidationResultsIsSoftError == true都会被收录,但不会被统计。我知道这听起来很奇怪,但概念是在 x 错误后无需继续验证文件,但验证可以 return 无限制 "warnings".

Enumeration 的枚举次数不要超过一次,这一点很重要,因为它 CPU-heavy。下面是我要更改的代码。

private IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
    var validationErrors = new List<IValidationResult>();
    int maxNumberOfErrors = 10;
    foreach (IDeliveryValidator deliveryValidator in _validators)
    {
       // Here I want results to contain MAX 10 regular validation results, but unlimited soft validation results
        IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject).Take(maxNumberOfErrors);
        validationErrors.AddRange(results);
        if (validationErrors.Count(x => !x.IsSoftError) >= maxNumberOfErrors)
        {
            return validationErrors.Take(maxNumberOfErrors).ToList();
        }
    }
    return validationErrors;
}

编辑: 当我遇到 10 'hard' 个错误时,我想完全停止循环。这里的主要问题是循环 在发生 10 'soft' 个错误时不会停止。

//Go through all the items and sort them into Soft and NotSoft
//But ultimately these are in memory constructs...so this is fast.
var foo = Validate(delivery).ToLookup(x => x.IsSoftError);
var soft = foo[true];
var hard = foo[false].Take(10);
var result = Enumerable.Concat(soft, hard);

如果您想在 10 'hard' 次错误后完全停止,您可以试试这个:

int count = 0;
IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject)
    .TakeWhile(error => error.IsSoftError || count++ < maxNumberOfErrors);

这将在遇到第 11 个硬错误时停止。