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

时间:2011-06-03 06:40:53

标签: c# parsing exception constructor

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

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

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

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

try
{
    obj = Constructor(sourceFile);
    ... process object ...
}
catch (IOException ex)
{
    ...
}

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

obj = Constructor();
obj.ReadData(sourceFile);
if (obj.IsValid)
{
    ... process object ...
}

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

obj = Constructor();
try
{
    obj.Read(sourceFile);
    ... process object ...
}
catch
{
    ...
}

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

if (ObjClass.TryParse(sourceFile, out obj))
{
    ... process object ...
}

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

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

5 个答案:

答案 0 :(得分: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;
    }
}

然后像这样使用它:

ObjClass.CreateFromFile(sourcefile);

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

更新:正如评论中所提到的,更好的模式是:

    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;
    }

答案 1 :(得分:13)

我不会将任何内容放入可能引发异常的构造函数中 - 除非出现问题 如果构造函数的可能返回值不是有效对象,则应将其封装。

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

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

答案 2 :(得分: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
  }
}

答案 3 :(得分:5)

来自Microsoft Constructor Design Guidelines (MSDN)

  

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

     

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


工厂方法不是解决此问题的正确方法。见Constructors vs Factory Methods

来自Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries

  

5.3构造函数设计

     

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

     

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


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

例如,当列表的容量参数为负时,List Constructor (Int32)会抛出ArgumentOutOfRangeException

var myList = new List<int>(-1); // throws ArgumentOutOfRangeException

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


更多信息

答案 4 :(得分:3)

所有这些解决方案都有效,但正如您所说,C#不允许从构造函数返回null。你得到一个对象或一个例外。由于这是C#的方式,我不会选择选项3,因为它只是模仿你正在谈论的其他语言。

很多人 [edit]其中就是Martin,正如我在他的回答中所读到的那样:) [/ edit] 认为保持构造函数干净小巧是件好事。我不太确定。如果没有该数据,您的对象没有用,您也可以读入构造函数中的数据。如果你想构造对象,设置一些选项,然后读取数据(特别是如果读取失败则可以再次尝试),单独的方法也可以。所以选项2也是一个很好的可能性。甚至更好,主要取决于口味。

所以只要你不选择3,选择你最舒服的那个。 :)