it-swarm.cn

C#中的构造函数参数验证-最佳做法

构造函数参数验证的最佳实践是什么?

假设有一个简单的C#:

public class MyClass
{
    public MyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            throw new ArgumentException("Text cannot be empty");

        // continue with normal construction
    }
}

抛出异常是否可以接受?

我遇到的替代方法是在实例化之前进行预验证:

public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
        {
            MessageBox.Show("Text cannot be empty");
            return null;
        }
        else
        {
            return new MyClass(text);
        }
    }
}
35
MPelletier

我倾向于在构造函数中执行所有验证。这是必须的,因为我几乎总是创建不可变的对象。对于您的特定情况,我认为这是可以接受的。

if (string.IsNullOrEmpty(text))
    throw new ArgumentException("message", "text");

如果您使用的是.NET 4,则可以执行此操作。当然,这取决于您是否认为仅包含空格的字符串是无效的。

if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("message", "text");
27
ChaosPandion

许多人指出,构造函数不应抛出异常。例如,在 此页面 上的KyleG就是这样做的。老实说,我想不出为什么。

在C++中,从构造函数中引发异常是一个坏主意,因为它会给您分配的内存包含未引用的未初始化对象(即,这是经典的内存泄漏)。这可能是耻辱的源头-一群老派的C++开发人员将他们的C#学习提高了一半,只是将他们从C++中学到的知识应用到了它。相反,在Objective-C中,Apple将分配步骤与初始化步骤分开,因此使用该语言的构造函数可以抛出异常。

C#无法从对构造函数的不成功调用中泄漏内存。甚至.NET框架中的某些类都将在其构造函数中引发异常。

23
Ant

抛出异常IFF,就其语义使用而言,无法将类置于一致状态。否则不要。切勿让对象以不一致的状态存在。这包括不提供完整的构造函数(例如在实际完全构建对象之前有一个空的构造函数+ initialize())...请说不!

紧要关头,每个人都在做。前几天,我在狭窄范围内对一个使用范围非常狭窄的对象进行了此操作。在未来的某一天,我或其他人可能会在实践中为此单支付费用。

我应该注意,“构造函数”是指客户端调用以构建对象的东西。除了名称为“ Constructor”的实际构造之外,这也很容易。例如,在C++中这样的事情不会违反IMNSHO原理:

struct funky_object
{
  ...
private:
  funky_object();
  bool initialize(std::string);

  friend boost::optional<funky_object> build_funky(std::string);
};
boost::optional<funky_object> build_funky(std::string str)
{
  funky_object fo;
  if (fo.initialize(str)) return fo;
  return boost::optional<funky_object>();
}

由于创建funky_object的唯一方法是调用build_funky,因此即使实际的“构造函数”未完成工作,也永远不会保留无效对象的原样。

尽管获得了可疑的收益(甚至可能是损失),但还有很多额外的工作要做。我还是更喜欢例外路线。

13
Edward Strange

在这种情况下,我将使用工厂方法。基本上,将您的类设置为仅具有私有构造函数,并具有一个返回对象实例的工厂方法。如果初始参数无效,则只需返回null并让调用代码确定要执行的操作。

public class MyClass
{
    private MyClass(string text)
    {
        //normal construction
    }

    public static MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            return null;
        else
            return new MyClass(text);
    }
}
public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        var cls = MyClass.MakeMyClass(text);
        if(cls == null)
             //show messagebox or throw exception
        return cls;
    }
}

除非情况特殊,否则不要抛出异常。我认为在这种情况下,可以轻松传递空值。如果真是这样,使用此模式将避免在保持MyClass状态有效的同时避免异常和性能损失。

9
Donn Relacion
  • 构造函数不应有任何副作用。
    • 除了私有字段初始化外,任何其他事情都应视为副作用。
    • 具有副作用的构造函数破坏了单一职责原则(SRP),并且与面向对象编程(OOP)的精神背道而驰。
  • 构造函数应该轻巧,绝不能失败。
    • 例如,当我在构造函数中看到try-catch块时,我总是发抖。构造函数不应引发异常或记录错误。

一个人可以合理地质疑这些准则,然后说:“但是我不遵守这些规则,我的代码工作正常!”为此,我会回答:“那可能是正确的,直到事实并非如此。”

  • 构造函数内部的异常和错误是非常意外的。除非被告知这样做,否则将来的程序员将不会倾向于将这些构造函数调用包含在防御性代码中。
  • 如果生产中有任何失败,则可能难以解析生成的堆栈跟踪。堆栈跟踪的顶部可能指向构造函数调用,但是在构造函数中发生了很多事情,并且可能没有指向失败的实际LOC。
    • 在这种情况下,我已经解析了许多.NET堆栈跟踪。
2
Jim G.

我的偏好是设置一个默认值,但我知道Java具有“ Apache Commons”库,该库可以执行类似的操作,而且我认为这也是一个不错的主意。我看不到如果无效值会使对象处于无法使用的状态,则会引发异常;字符串不是一个很好的例子,但是如果它用于穷人的DI呢?如果传入了null的值,则无法操作可以说,代替ICustomerRepository接口在这种情况下,抛出异常是正确的处理方式。

0
Wayne Molina

这取决于MyClass在做什么。如果MyClass实际上是数据存储库类,而参数文本是连接字符串,则最佳实践将是引发ArgumentException。但是,如果MyClass是StringBuilder类(例如),则可以将其留空。

因此,这取决于方法的基本参数文本-对象是否具有空值或空值有意义?

0
Steven Striga