C# 如何构建流畅的嵌套Guard API

C# 如何构建流畅的嵌套Guard API,c#,lambda,fluent,C#,Lambda,Fluent,我正在构建一个简单的Guard API来防止非法参数被传递给函数等等 我有以下代码: public static class Guard { public static GuardArgument<T> Ensure<T>(T value, string argumentName) { return new GuardArgument<T>(value, argumentName); } } public class

我正在构建一个简单的Guard API来防止非法参数被传递给函数等等

我有以下代码:

public static class Guard
{
    public static GuardArgument<T> Ensure<T>(T value, string argumentName)
    {
        return new GuardArgument<T>(value, argumentName);
    }
}

public class GuardArgument<T>
{
    public GuardArgument(T value, string argumentName)
    {
        Value = value;
        Name = Name;
    }

    public T Value { get; private set; }
    public string Name { get; private set; }
}

// Example extension for validity checks
public static GuardArgument<T> IsNotNull<T>(this GuardArgument<T> guardArgument, string errorMessage)
{
    if (guardArgument.Value == null)
    {
        throw new ArgumentNullException(guardArgument.Name, errorMessage);
    }

    return guardArgument;
}     
这一切都很好。我现在希望能够通过以下方式扩展API以在检查中包括子属性:

Guard.Ensure(someObject, "someObject")
    .IsNotNull()
    .Property(
        (x => x.ChildProp1, "childProp1")
           .IsNotNull()
           .IsGreaterThan(10)
     )
     .Property(
        (x => x.ChildProp2, "childProp2")
           .IsNotNull()
           .IsLessThan(10)
     );
显然,新的
.Property
方法需要返回父级
GuardArgument
,以便链接。此外,子属性需要能够使用现有的检查方法(
IsNotNull()
etc)来避免代码重复


我无法确定如何构造lambda/Property函数参数,或者
.Property
方法应该位于何处,即它应该是
GuardArgument
或其他地方的属性,或者,即使API有更好的结构。

我认为将属性检查放在父对象检查链中对您没有好处。因此,我建议为父对象创建一个链,为每个属性创建另一个链。这更具可读性:

  Guard.Ensure(a, "a")
    .IsNotNull("a is null");
  Guard.Ensure(a.p0, "a.p0")
    .IsGreaterThan(10);
  Guard.Ensure(a.p1, "a.p1")
    .IsGreaterThan(5);

我想你是在改造一个轮子。安装此扩展-下面是如何使用它

除了与您类似的基于代码的资产外,例如:

 public int[] Bar(){
   Contract.Ensures( Contract.ForAll(0, Contract.Result<int[]>().Length, index => Contract.Result<int[]>()[index] > 0));

 ....
 }
public int[]Bar(){
Contract.survey(Contract.ForAll(0,Contract.Result().Length,index=>Contract.Result()[index]>0));
....
}

Contract.Requires(x.Value.NestedObject!=null,“x.Value.NestedObject”);

但也有属性和广泛的功能集检查接口,良好的前置和后置条件等检查出来

以下函数允许使用与所需语法类似的语法

public static GuardArgument<T> Property<T, TProp>(this GuardArgument<T> guardArgument, Func<T, TProp> getProperty, string propertyName, Action<GuardArgument<TProp>> validate)
{
    GuardArgument<TProp> propertyGuardArgument = new GuardArgument<TProp>(getProperty(guardArgument.Value), propertyName);

    validate(propertyGuardArgument);

    return guardArgument;
}

可能是输入错误,但GuardArgument构造函数设置
Name=Name
您会说,“显然,新的
.Property
方法需要返回父
GuardArgument
才能链接。”-不,这不正确。如果你试图创建一个流畅的API,那么你不应该通过父级-你应该构建一个新的
GuardArgument
来嵌套父级,否则你可以通过沿链保留引用,然后尝试从中构建新的链来创建可怕的bug。我在一个项目中使用过代码契约,发现使用它们很痛苦。他们将建造速度降低到爬行速度。几个月后,我们把它们拿出来,转而问类似的问题。谢谢@Sean,我们最近开始使用它们,而且没有太多,所以还没有注意到构建时间变得更糟。我已经看过代码契约,和Sean一样,构建速度很慢,只是真正用于保护方法输入。因此需要重量轻得多的东西……看起来像JavaScript“末日金字塔”;)无论如何,请记住,您可以将其展平,首先在fluent之外声明和创建Func和Action,然后将它们传递给Property()。
 Contract.Requires<ArgumentNullException>( x.Value.NestedObject != null, ”x.Value.NestedObject” );
public static GuardArgument<T> Property<T, TProp>(this GuardArgument<T> guardArgument, Func<T, TProp> getProperty, string propertyName, Action<GuardArgument<TProp>> validate)
{
    GuardArgument<TProp> propertyGuardArgument = new GuardArgument<TProp>(getProperty(guardArgument.Value), propertyName);

    validate(propertyGuardArgument);

    return guardArgument;
}
Guard.Ensure(someObject, "someObject")
     .IsNotNull()
     .Property(x => x.ChildProp1, "childProp1", childProp1 =>
         childProp1.IsNotNull()
                   .IsLessThan(10)
                   .Property(y => y.InnerChildProperty, "innerChildProperty", innerChildProperty =>
                       innerChildProperty.IsNotNull()
                    )
     )
     .Property(x => x.ChildProp2, "childProp2", childProp2 =>
         childProp2.IsNotNull()
                   .IsGreaterThan(10)
     );