应该在哪里抛出异常?

时间:2013-07-18 11:36:07

标签: c# .net exception

我有一个看起来如下的课程:

public class StackOverflowQuestion {

    private string _question;

    public string Question {
        get { return _question;  }
        set { _question = value; }
    }

    public StackOverflowQuestion(string question) {
        _question = question;
    }

    public override string ToString() {
        return _question;
    }
}

现在,值“question”不允许为null或为空,并且应该通过ArgumentNullException通知用户 - 但是应该在哪里抛出它?根据'快速失败'原则 - >无处不在。

public class StackOverflowQuestion {

    private string _question;

    public string Question {
        get { return _question;  }
        set { 
           if(!String.IsNullOrEmpty(value))
                _question = value
            else throw new ArgumentNullException("value");
        }
    }

    public StackOverflowQuestion(string question) {
        if(!String.IsNullOrEmpty(question))
            _question = question;
        else throw new ArgumentNullException("question");
    }

    public override string ToString() {
        if(!String.IsNullOrEmpty(_question)) return _question;
        else throw new ArgumentNullException("_question");
    }
}

现在这显然是荒谬的,极其重复。但似乎是正确的:如果值是通过.ctor设置的,它会在短暂检查后直接失败。当它通过属性设置时,它会在短暂检查后直接失败..但谁会在setter上预期异常?当我输出字符串时,我期待一个字符串,而不是很久以前应该发生的事情的例外,但是再次:如果它错了,它应该尽快失败,即使'很快'已经很晚了。

那么,唯一的异常处理应该在哪里完成?我要求“最佳实践”,还是这是一种品味的东西?

3 个答案:

答案 0 :(得分:3)

由于_question是私有的,因此无需在ToString()中检查它是否为null(除非您只是检查自己的代码)。

您可以通过让构造函数使用属性setter来避免检查构造函数。因此,我建议:

public class StackOverflowQuestion {

    private string _question;

    public string Question {
        get { return _question;  }
        set { 
           if(string.IsNullOrEmpty(value))
                // to make this more transparent when thrown through the constructor, it might
                // be preferable to throw a real error message like "Question: cannot be null or empty"
                throw new ArgumentException("value");
           this._question = value;
        }
    }

    public StackOverflowQuestion(string question) {
        this.Question = question;
    }

    public override string ToString() {
        return this.Question;
    }
}

有几点需要注意: 1.对于空字符串,您应该抛出ArgumentException而不是ArgumentNullException(如果您希望可以执行2次检查,仍然会将ArgumentNullException抛出为空值)。 2.虽然该方法使用较少的代码,但一个缺点是用户获取的错误消息比将它们传递给构造函数时更差,因为失败发生在2级而不是1级。

答案 1 :(得分:2)

您只需要在中测试 - 在您设置变量的单个位置,更改构造函数以使用该属性:

public class StackOverflowQuestion
{
    private string _question;

    public string Question
    {
        get { return _question; }
        set
        { 
           if (String.IsNullOrEmpty(value))
           {
               throw new ArgumentException("Question cannot be null or empty",
                                           "value");
           }
           _question = value;
        }
    }

    public StackOverflowQuestion(string question)
    {
        Question = question;
    }

    public override string ToString()
    {
        return Question;
    }
}

这里的一个缺点是,“错误参数”名称将是value而不是question,当它在构造函数中为null时,但我认为这是一个值得付出的代价。另一种方法是来使用该消息,而不是指定参数名称。

希望将nullempty区分开来,以便在ArgumentNullException为空时抛出ArgumentNullException - 但你不应该抛出{{1}}当它只是空的时候。

获取值时,您不需要执行任何检查,因为您知道它永远不会为null或为空,因为您正在阻止它以这种方式设置。

您还应该考虑是否可以使该类不可变,此时您只需要在构造函数中进行测试,因为没有setter ...

答案 2 :(得分:1)

我宁愿让它变成不可变的:

public class StackOverflowQuestion
    {
        public string Question
        {
            get; private set;
        }

        public StackOverflowQuestion(string question)
        {
            if (String.IsNullOrEmpty(question))                
              throw new ArgumentNullException("question");

            Question = question;
        }

        public override string ToString()
        {
            return Question;
        }
    }