在C#构造函数中处理损坏的输入数据的最合适方法是什么?

23 c# parsing constructor exception

我正在从文件中读取数据并根据此数据创建对象.数据格式不受我的控制,偶尔也会损坏.在C#中构造对象时,最合适的处理这些错误的方法是什么?

在其他编程语言中,我返回了一个null,但这似乎不是C#的一个选项.

我已经设法找出以下选项,但我希望更有经验的C#程序员的建议:

选项1.读取构造函数内的文件,并在源数据损坏时抛出异常:

try
{
    obj = Constructor(sourceFile);
    ... process object ...
}
catch (IOException ex)
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

选项2.创建对象,然后使用方法从源文件中读取数据:

obj = Constructor();
obj.ReadData(sourceFile);
if (obj.IsValid)
{
    ... process object ...
}
Run Code Online (Sandbox Code Playgroud)

或者可能在出错时抛出异常:

obj = Constructor();
try
{
    obj.Read(sourceFile);
    ... process object ...
}
catch
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

选项3.使用静态TryParse方法创建对象:

if (ObjClass.TryParse(sourceFile, out obj))
{
    ... process object ...
}
Run Code Online (Sandbox Code Playgroud)

如果是这样,我应该在内部使用选项1实现选项3吗?

public static bool TryParse(FileStream sourceFile, out ObjClass obj)
{   
    try
    {
        obj = Constructor(sourceFile);
        return true;
    }
    catch (IOException ex)
        return false;
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*Wue 19

我会按照选项3)的方式做一些事情:

class ObjectClass
{
    protected ObjectClass(...constructor parameters your object depends on...)
    {
    }

    public static ObjectClass CreateFromFile(FileStream sourceFile)
    {
        .. parse source file
        if (parseOk)
        {
            return new ObjectClass(my, constructor, parameters);
        }
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

ObjClass.CreateFromFile(sourcefile);
Run Code Online (Sandbox Code Playgroud)

通常,构造函数应该将所有基本上定义类的属性作为参数.执行重量级计算(如解析文件)最好留给工厂方法,因为构造函数通常不希望执行复杂且可能长时间运行的任务.

更新:如评论中所述,更好的模式是:

    public static ObjectClass CreateFromFile(FileStream sourceFile)
    {
        .. parse source file
        if (!parseOk)
        {
            throw new ParseException(parseErrorDescription);
        }
        return new ObjectClass(my, constructor, parameters);
    }

    public static bool TryCreateFromFile(FileStream sourceFile, out ObjectClass obj)
    {
        obj = null;
        .. parse source file
        if (!parseOk)
        {
            return false;
        }
        obj = new ObjectClass(my, constructor, parameters);
        return true;
    }
Run Code Online (Sandbox Code Playgroud)


Mar*_*ngs 13

我不会把任何东西放到可能抛出异常的构造函数中 - 除非出现问题.
如果构造函数具有可能的返回值而不是有效对象,则应该封装它.

最安全的方法可能是创建一个工厂方法(类中的公共静态函数接受文件引用并返回该类的新实例或null).此方法应首先验证文件及其数据,然后才创建新对象.

如果文件数据具有简单结构,则可以先将其加载到某个局部变量中,然后使用此数据构造对象.否则,您仍然可以决定 - 在您的工厂方法内 - 如果您想要尝试/捕获构造或使用上述任何其他点.

  • 真?构造函数通常会抛出ArgumentNullException,ArgumentOutOfRangeException等.如果对象应该公开一致的数据,那么它的构造函数应该验证该数据. (3认同)

Jar*_*Par 5

选项#1和#3都是很好的选择,在.Net框架中很常见.为同一类型提供两者也是很常见的.考虑Int32.TryParseInt32.Parse.提供两者可以为开发人员提供更大的灵活性,而不会影响类型的完整性.

我强烈建议你避免#2.此模式强制类型作者和类型使用者在多个状态中处理该类型的实例

  • 构建但未完全初始化
  • 已初始化且有效
  • 已初始化且无效

这给每个消费者带来了处理所有不同状态的实例的负担(即使响应只是抛出).此外,它迫使消费者采用非标准模式.开发人员必须了解您的类型是特殊的,并且需要构建然后进行初始化.它违背了.Net中创建对象的标准方式.

注意#3虽然我会接近它有点不同.异常表单应该以try表单的形式实现.这是向用户提供这两个选项时的标准模式.考虑以下模式

class MyType { 
  struct ParsedData { 
    // Data from the file
  }

  public MyType(string filePath) : this(Parse(filePath)) { 
    // The Parse method will throw here if the data is invalid
  }

  private MyType(ParsedData data) {
    // Operate on the valid data.  This doesn't throw since the errors
    // have been rooted out already in TryParseFile
  }

  public static bool TryParse(string filePath, out MyType obj) {
    ParsedData data;
    if (!TryParseFile(filePath, out data)) {
      obj = null;
      return false;
    }

    obj = new MyType(data);
    return true;
  }

  private static ParsedData Parse(string filePath) {
    ParsedData data;
    if (!TryParseFile(filePath, out data)) {
      throw new Exception(...);
    }
    return data;
  }

  private static bool TryParseFile(string filePath, out ParsedData data) {
    // Parse the file and implement error detection logic here
  }
}
Run Code Online (Sandbox Code Playgroud)


Dev*_*van 5

Microsoft构造函数设计指南(MSDN),

如果合适,请从实例构造函数中抛出异常.

构造函数应该像任何方法一样抛出和处理异常.具体来说,构造函数不应该捕获并隐藏它无法处理的任何异常.


Factory Method不是解决此问题的正确方法.请参阅构造函数与工厂方法

来自框架设计指南:可重用.NET库的约定,成语和模式

5.3构造函数设计

如果所需操作的语义不直接映射到新实例的构造,或者遵循构造函数设计指南感觉不自然,请考虑使用静态工厂方法而不是构造函数.

如果合适,请从实例构造函数中抛出异常.


.NET BCL实现会从构造函数中抛出异常

例如,列表构造函数(Int32)在列表的capacity参数为负数时抛出ArgumentOutOfRangeException.

var myList = new List<int>(-1); // throws ArgumentOutOfRangeException
Run Code Online (Sandbox Code Playgroud)

同样,构造函数在读取文件时应抛出适当类型的异常.例如,如果文件在指定位置不存在,它可能抛出FileNotFoundException等.


更多信息