C# 复杂类型的方法参数验证

C# 复杂类型的方法参数验证,c#,validation,arguments,complextype,C#,Validation,Arguments,Complextype,我非常喜欢方法中的论证验证。在我的代码中通常可以看到这样的情况: public void Foo(string someString) { #region parameter validation if(string.IsNullOrWhiteSpace(someString)) throw new ArgumentNullException("someString"); // Let's ignore the .NET 4.6 nameof-fe

我非常喜欢方法中的论证验证。在我的代码中通常可以看到这样的情况:

public void Foo(string someString)
{
    #region parameter validation
    if(string.IsNullOrWhiteSpace(someString))
        throw new ArgumentNullException("someString");
        // Let's ignore the .NET 4.6 nameof-feature for the sake of this question.
    #endregion

    // do things
}
现在让我们假设该方法不接受字符串,而是一个复杂对象。假设我希望该方法验证是否设置了这个复杂对象的属性。正确的方法是什么?到目前为止,我是这样做的:

public void Foo(Person person)
{
    #region parameter validation
    if(person == null)
        throw new ArgumentNullException("person");
    if(string.IsNullOrWhiteSpace(person.Name))
        throw new ArgumentNullException("person.Name");
    #endregion

    // do things
}
if (person == null)
    throw new ArgumentNullException (nameof (person));

// Validates only 1 property.
if (!person.Validate (nameof (person.MyProperty)))
    DisplayErrors (person);

// Validates all properties.
if (!person.Validate ())
    DisplayErrors (person);
int? length = customers?.Length;

这方面有什么最佳实践吗?
ArgumentNullException
是正确的方法吗?是否建议这样检查属性?

在我的例子中,我所有的模型都有一个验证方法,这样我就调用它,如果它返回false(这意味着我有错误),我调用另一个方法从中获取错误并向用户显示,类似这样:

public void Foo(Person person)
{
    #region parameter validation
    if(person == null)
        throw new ArgumentNullException("person");
    if(string.IsNullOrWhiteSpace(person.Name))
        throw new ArgumentNullException("person.Name");
    #endregion

    // do things
}
if (person == null)
    throw new ArgumentNullException (nameof (person));

// Validates only 1 property.
if (!person.Validate (nameof (person.MyProperty)))
    DisplayErrors (person);

// Validates all properties.
if (!person.Validate ())
    DisplayErrors (person);
int? length = customers?.Length;
其背后的原因是,在大多数情况下,当用户填写信息时,我必须允许模型无效。我将这些方法与用于WPF的接口INotifyDataErrorInfo一起使用,以使UI在出现错误时能够刷新


当一个方法或操作只需要对象的一部分时,facade模型是一种解决方案,您可以在其中创建一个新类来封装更复杂的类,facade类也有Validate方法来进行部分验证。

对我来说,对于99%的情况来说,这听起来像是一个设计问题。没有名字的人有效吗?我想不是。如果不是,为什么你允许一个人被创造而没有一个人?如果你(出于某种原因)允许它,那么维护它自己的“有效性”不是对象的责任吗?我倾向于用数据注释来标记元数据,这些标记是必需的状态,并且简单地检查传入的人是“有效的”

您可以考虑。当然,如您所示,检查是验证参数的一种非常好的方法。。。假设您只验证正在使用的字段。验证未引用的字段会带来麻烦(除非这是该方法的明确目的)

最新版本的C#提供了一个“?”操作符,它允许您执行以下操作:

public void Foo(Person person)
{
    #region parameter validation
    if(person == null)
        throw new ArgumentNullException("person");
    if(string.IsNullOrWhiteSpace(person.Name))
        throw new ArgumentNullException("person.Name");
    #endregion

    // do things
}
if (person == null)
    throw new ArgumentNullException (nameof (person));

// Validates only 1 property.
if (!person.Validate (nameof (person.MyProperty)))
    DisplayErrors (person);

// Validates all properties.
if (!person.Validate ())
    DisplayErrors (person);
int? length = customers?.Length;

这允许您保护自己不受空引用异常的影响,在某些情况下可能比预验证更可取

哦,运行代码分析工具通常会在验证参数之前使用参数进行标记,所以至少这些工具认为这是一种最佳实践

更新(回应评论): 这在很大程度上取决于你想做什么。如果您正在创建一个API,并且希望返回尽可能最好的错误消息,那么您所建议的可能是您所能做到的最好的。这假设您主要关心与API的使用者的通信

如果您的主要兴趣是确保提供给您的方法的数据是有效的,并且不会导致您的方法行为不良(CYA),那么您应该查看合同。契约的优点是提供了post条件(确保您的方法不会返回意外的结果)和不变条件(确保没有更改您不想更改的内容)。契约既可以提供静态(编译时)保证,也可以提供运行时保证。有一种相当细粒度的方法来管理合同选项。运行时契约冲突会产生人们可能不太熟悉的契约异常。用户也可能不熟悉编译时的投诉。 这假设您主要关心的是确保正确的操作,或者不会因为滥用API而受到指责


您可以同时使用这两种方法,例如使用契约进行编译时检查,并使用更简单的“如果”检查进行运行时检查

似乎没有官方正确的方法来做这件事,所以我会坚持我自己的方法,那就是

if(myComplexObject.SomeProperty == null)
    throw new ArgumentNullException("myComplexObject.SomeProperty");
打电话的人会立即知道出了什么问题

请注意,我没有使用operator的名称,因为这同样只会给我
SomeProperty
,而不是
myComplexObject.SomeProperty
。我可以做
nameof(myComplexObject)+““+nameof(myComplexObject.SomeProperty)”,但我需要再考虑一下,乍一看是不对的

以下是我认为其他方法比我的更糟糕的原因:

有人说我应该在模型中进行验证,我不会这样做,因为模型不知道如何对不知道的方法验证对象。如果是整体对象实例的有效性,当然,请将其保留在模型中,或者如果您要求我甚至是业务层进行业务验证,但情况并非如此

其他人说我可以在C#中使用空合并操作符(
?。
),我也不会这样做,因为这不是“如果为空,那么就使用该”场景,而是“如果为空,我不会做任何事情,您应该为给我空值感到难过,修复您的代码”

还有代码契约,它提供了一种验证参数的好方法,并有一些真正好的好处,但最终还是出现了同样的问题:我如何告诉调用方一个复杂对象的哪个值是无效的?他需要知道参数Y的属性X是错误的,尽管代码契约不是唯一的回答我的问题,我想我的答案也可以用在那里,如果同样的情况发生的话


然后有人建议我不应该在那个地方验证对象的属性,而应该在需要的函数中验证。问题是,该属性几乎不作为单独的值传递给另一个函数。另外,它会与接口的概念发生冲突。

如果检查真的很重要,请您与n是否在用于运行这些测试的每个类中放置一个静态方法?是否可能创建一个需要对属性运行测试的方法的接口?
ArgumentNullException
假设参数为
null
,而不是空白字符串。@kailanjian:否,因为验证