C# 在这种情况下,我应该如何处理无效的用户输入?

C# 在这种情况下,我应该如何处理无效的用户输入?,c#,validation,exception,null,C#,Validation,Exception,Null,我正在为一个几何建模程序编写一个插件,我有一个基于曲线对象的抽象类。只有当曲线是平面的、闭合的且不与自身相交时,才认为该曲线有效 然后,我有一系列其他方法引用这条曲线,这些方法可以从曲线中生成曲面,或者将其挤出到体积中。如果在BaseCurve无效时调用这些方法,它们将引发异常。现在,如果用户使其中一条曲线无效,我的程序就会崩溃,我正试图找出让我的程序处理这个无效输入的最佳方法 下面是我的班级的样子: public abstract class AbsCurveBasedObject {

我正在为一个几何建模程序编写一个插件,我有一个基于曲线对象的抽象类。只有当曲线是平面的、闭合的且不与自身相交时,才认为该曲线有效

然后,我有一系列其他方法引用这条曲线,这些方法可以从曲线中生成曲面,或者将其挤出到体积中。如果在BaseCurve无效时调用这些方法,它们将引发异常。现在,如果用户使其中一条曲线无效,我的程序就会崩溃,我正试图找出让我的程序处理这个无效输入的最佳方法

下面是我的班级的样子:

public abstract class AbsCurveBasedObject
{
    public abstract Curve BaseCurve
    {
        get;
    }

    public bool BaseCurveIsValid
    {
        get
        {
            Curve c = this.BaseCurve;
            ...
            //checks that curve is valid
            ...
            return true/false;
        }
    }

    public Surface GetSurface()
    {
         Curve c = this.BaseCurve();
         ...
         //magic that converts c to a surface
         //exception is thrown if c is invalid
         ...
         return surface;
    }

    public Surface GetVolume()
    {
         Surface s = this.GetSurface();
         ...
         //magic that converts s into a volume
         ...
         return volume;
    }
}
如果曲线无效,我不确定GetSurface()是否应返回NULL,或者是否应抛出异常

基本曲线无效并不意外,因为我知道用户在使用我的程序时最终会创建一条无效曲线。我的理解是,异常通常只应该在程序到达某个点时抛出,该点发生了意外的事情,并且它不知道如何继续

如果曲线无效,我应该从GetSurface()返回NULL,如果GetSurface()无效,那么基于GetSurface()的每个方法也应该返回NULL吗?这似乎将更难调试。我知道我最终会忘记检查某个地方的返回值是否为NULL,并最终导致一些ArgumentNullException,它会一直跟踪到AbsCurveBasedObject.GetSurface()


因此,当用户如何使基本曲线属性无效时,是否最好在整个位置跟踪中使用if/else或try/catch块来处理?

官方C#说抛出一个异常,让用户界面层处理该异常。

理想情况下,如果可以避免,您永远不想抛出异常。然而,这并不一定意味着返回null是正确的做法!这样做可能是错误的

这样看。您的代码可能如下所示:

Blah MakeMeABlah(Foo foo)
{
  if (!IsValid(foo)) throw new InvalidArgumentException("foo");
  // [make a blah from a foo]
}
Foo foo = GetFooFromUser();
try
{
    blah = MakeMeABlah(foo);
}
catch(...)
{
   // Tell user that input foo was invalid
}
您可以使调用方看起来像这样:

Blah MakeMeABlah(Foo foo)
{
  if (!IsValid(foo)) throw new InvalidArgumentException("foo");
  // [make a blah from a foo]
}
Foo foo = GetFooFromUser();
try
{
    blah = MakeMeABlah(foo);
}
catch(...)
{
   // Tell user that input foo was invalid
}
那不太好。解决问题的方法是将IsValid设置为公共方法:

Foo foo = GetFooFromUser();
if (!IsValid(foo))
   // Tell user that input foo was invalid
else
   blah =  MakeMeABlah(foo);

而且,没有异常处理,代价是必须调用两次IsValid。如果验证很便宜,谁在乎呢?如果验证成本很高,那么您可能需要进一步重构,使MakeMeABlah的内部版本不会在发布版本中重新进行检查。

如果曲线无效,为什么要构建曲线。使用构建器模式,您可以公开仅构建有效曲线对象的功能,并允许UI处理其中的无效状态

这就是(我认为)Eric建议的精髓,在构造无效对象之前,公开一种验证状态的外部方法


注意:一旦您只构建了有效的曲线对象,您就可以删除创建布尔值“bool BaseCurveIsValid”时的可怕选项:)

在优秀的《框架设计指南》第二版中有一个非常好的讨论。它讨论了Tester-Doer模式(参见Eric Lippert的回答——缺点是线程问题)和Try-Parse模式的优缺点,以及许多异常指南。通常不建议返回null。