Общий метод проверки c#

#c#

#c#

Вопрос:

У меня есть этот текущий метод, который выглядит следующим образом:

 public static class ThrowIf
{
    public static void ArgumentIsNull(params Expression<Func<object>>[] exprs)
    {
        foreach (var expr in exprs)
        {
            var member = expr.Body as MemberExpression;
            var name = member?.Member.Name ?? "Unknown";
            var value = expr.Compile()();

            if (value == null) throw new ArgumentNullException(name);
        }
    }
}
  

Что замечательно. Я могу вызвать его следующим образом:

 ThrowIf.ArgumentIsNull(() => model, () => model.Lines);
  

Вместо того, чтобы делать это:

 If (model == null) throw new ArgumentNullException(nameof(model));
If (model.Lines == null) throw new ArgumentNullException(nameof(model.Lines));
  

Вдохновленный этим методом расширения, я решил создать метод проверки. Я сделал это следующим образом:

 public static class ValidateIf
{
    public static string IsNullOrEmpty(params Expression<Func<object>>[] exprs)
    {
        foreach (var expr in exprs)
        {
            var member = expr.Body as MemberExpression;
            var name = member?.Member.Name ?? "Unknown";
            var value = expr.Compile()();

            if (value == null) return $"{name} cannot be null.";
        }

        return null;
    }
}
  

И я, не задумываясь, использовал его следующим образом:

 ValidateIf.IsNullOrEmpty(
    () => line.ProductCode,
    () => line.Currency,
    () => line.Dates);
  

Проблема с этим заключается в том, что возвращаемый тип не используется.
Я должен был бы сделать что-то вроде этого:

 return ValidateIf.IsNullOrEmpty(
    () => line.ProductCode,
    () => line.Currency,
    () => line.Dates);
  

Но в моем коде is используется примерно так:

 public string ValidateSynchronize(OrderViewModel model)
{
    ValidateIf.IsNullOrEmpty(
        () => model.Account,
        () => model.Account.OpeningTimes,
        () => model.Account.VehicleRouting,
        () => model.Lines
    );
    foreach (var line in model.Lines)
        ValidateIf.IsNullOrEmpty(
            () => line.Delivery,
            () => line.Delivery.OpeningTimes,
            () => line.Delivery.VehicleRouting,
            () => line.Product
        );

    return null;
}
  

Который должен был бы стать этим:

 public string ValidateSynchronize(OrderViewModel model)
{
    var message = ValidateIf.IsNullOrEmpty(
        () => model.Account,
        () => model.Account.OpeningTimes,
        () => model.Account.VehicleRouting,
        () => model.Lines
    );
    if (!string.IsNullOrEmpty(message)) return message;

    foreach (var line in model.Lines)
    {
        message = ValidateIf.IsNullOrEmpty(
            () => line.Delivery,
            () => line.Delivery.OpeningTimes,
            () => line.Delivery.VehicleRouting,
            () => line.Product
        );
        if (!string.IsNullOrEmpty(message)) return message;
    }

    return null;
}
  

Что немного некрасиво, и когда у меня есть такие методы, как этот:

 public async Task<string> ValidateSaveAsync(OrderViewModel model, IStockProvider stockProvider)
{
    ValidateIf.IsNullOrEmpty(
        () => model.Account,
        () => model.Account.Currency,
        () => model.AccountNumber,
        () => model.ReferenceNumber,
        () => model.Source,
        () => model.OrderDate,
        () => model.Type,
        () => model.Lines);

    var validationMessage = Validate(model);
    if (!string.IsNullOrEmpty(validationMessage))
        return validationMessage;

    foreach (var line in model.Lines)
    {
        ValidateIf.IsNullOrEmpty(
            () => line.Product,
            () => line.Product.ProductCode,
            () => line.UnitOfMeasure,
            () => line.Currency,
            () => line.Currency.UnitPrice,
            () => line.Currency.LineValue,
            () => line.Currency.IssueReference,
            () => line.Currency.IssueNumber,
            () => line.Dates);

        if (!ValidationExtensions.IsIssueNumberValid(line.Currency.IssueNumber)) return "Price Issue Number is invalid, must be greater than 0";
        if (!ValidationExtensions.IsRequiredDateValid(line.Dates.Required)) line.Dates.Required = DateTime.Now.AddDays(1).Date;
        if (!ValidationExtensions.IsDespatchDateValid(line.Dates.Dispatch)) line.Dates.Dispatch = DateTime.Now.Date;
        if (!ValidationExtensions.IsDeliveryDateValid(line.Dates.Delivery)) line.Dates.Delivery = DateTime.Now.Date;
        if (!ValidationExtensions.IsUnitPriceValid(line.Currency.UnitPrice)) return "Unit price is invalid, must be between 0 and 30";
        if (!ValidationExtensions.ValidateQuantity(line.Type, line.Quantity)) return $"Minimum of 1.5M and maximum of 22M order quantity on cut - lengths - Line:{line.Id}";
        if (!ValidationExtensions.IsRollAvailable(line.Type, line.UnitOfMeasure, line.Product.Roll30Available)) return $"30M rolls not allowed - Line:{line.Id}";

        if (string.IsNullOrEmpty(model.Type) || model.Type.Equals("S", StringComparison.CurrentCultureIgnoreCase)) continue;
        if (ValidationExtensions.IsProductDiscontinued(line.Product.Status))
            return $"Product discontinued - Line:{line.Id}";

        // TODO: Matt asked me to add this, see the comment below
        // I believe it is when they choose a particular lot number when ordering a roll as the qty for rolls is always 1 yet the stock can be
        // any size... for now if the ordered item type is a roll ignore this validation check.
        if (line.Type == OrderLineType.Roll || string.IsNullOrEmpty(line.LotNumber)) continue;

        var stock = await stockProvider.GetAsync(line.LotNumber);

        if (line.Quantity >= stock.Quantity) continue;

        return $"Selected lot {line.LotNumber} has changed - quantities no longer match (requested {line.Quantity} got {stock.Quantity}) Line {line.Id}";
    }

    return null;
}
  

Это полный бардак…
Итак, есть ли изящный трюк, который я мог бы использовать, или другой способ вернуть строку, только если у нее есть значение?
Я знаю, что это маловероятно, но я подумал, что хотел бы спросить, прежде чем внедрять уродливое решение: (

Комментарии:

1. «другой способ вернуть строку, только если у нее есть значение» — вы уже знаете способ сделать это — если это исключительное обстоятельство…

2. Возможно, было бы аккуратнее, если бы вы следовали шаблону ‘try’, подобному int.TryParse , т. е. if (ValidateIf.IsNullOrEmpty(x, out message)) { handle error } . Кроме исключений, не уверен, что еще вы можете сделать.

3. Просто чтобы вы знали, такой подход к выражению значительно замедлит ваш код. Компилировать что-либо в runtimeassembly только для проверки на наличие нулей немного излишне.

4. если вы используете ASP, почему бы не использовать проверку модели learn.microsoft.com/en-us/aspnet/web-api/overview /…

5. Я просто прикидывал, читая весь код. Чего я совершенно не понимаю, так это зачем вам вообще нужны выражения? Разве вы не можете просто ввести пустой аргумент null(параметры object[] o) и вернуть o.Any(x => x == null). Вызывающий может просто вызвать, например: ArgumentsisNull(модель, model?. Строки)