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

发布于 2024-11-14 04:44:19 字数 1079 浏览 1 评论 0 原文

我正在从文件中读取数据并根据该数据创建对象。数据格式不受我的控制,并且偶尔会损坏。在 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 ...
}

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

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

I'm reading data in from a file and creating objects based on this data. The data format is not under my control and is occasionally corrupt. What is the most appropriate way of handling these errors when constructing the objects in C#?

In other programming languages I have returned a null, but that does not appear to be an option with C#.

I've managed to figure out the following options, but I would appreciate advice from more experienced C# programmers:

Option 1. Read the file inside the constructor and throw an exception when the source data is corrupt:

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

Option 2. Create the object, then use a method to read data from the source file:

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

or possibly throw exceptions on error:

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

Option 3. Create the object using a static TryParse method:

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

and if so, should I implement option 3 internally using option 1?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

久夏青 2024-11-21 04:44: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;
    }

I would do something along the lines of option 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;
    }
}

And then use it like this:

ObjClass.CreateFromFile(sourcefile);

In general the constructor should take as parameters all properties which essentially define the class. Doing heavyweight calculations (like parsing a file) is best left to factory methods as it is usually not expected for the constructor to perform complex and potentially long running tasks.

Update: As mentioned in comments a better pattern is this:

    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;
    }
一个人练习一个人 2024-11-21 04:44:19

我不会将任何可能引发异常的内容放入构造函数中 - 除非出现真正错误。
如果你的构造函数有一个可能的返回值而不是一个有效的对象,你应该封装它。

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

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

I would not put anything into a constructor that might throw an exception - except for if something goes really wrong.
If your constructor has a possible return value other than a valid object, you should encapsulate it.

The safest way would probably be to create a factory method (public static function in the class that accepts a file reference and returns a new instance of the class or null). This method should first validate the file and its data and only then create a new object.

If the file data has a simple structure, you can first load it into some local variable and construct the object with this data.
Otherwise, you can still decide - inside of your factory method - if you rather want to try / catch the construction or use any of the other points mentioned above.

一江春梦 2024-11-21 04:44:19

选项 #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
  }
}

Both Options #1 and #3 are good choices and common in the .Net framework. It's also common to provide both for the same type. Consider Int32.TryParse and Int32.Parse. Providing both gives developers a bit more flexibility without detracting from the integrity of the type.

I would strongly advise you to avoid #2. This pattern forces both the type author and type consumer to handle instances of the type in multiple states

  • Constructed but not fully initialized
  • Initialized and valid
  • Initialized and invalid

This puts a burden on every consumer to deal with instances being in all different states (even if the response is to just throw). Additionally it forces a non-standard pattern on consumers. Developers have to understand your type is special and that it needs to be constructed and then initialized. It goes against the standard way objects are created in .Net.

Note for #3 though I would approach it a bit different. The exception form should be implemented in terms of the try form. This is the standard pattern when providing both options to the user. Consider the following pattern

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
  }
}
糖粟与秋泊 2024-11-21 04:44:19

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

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

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

工厂方法不是解决此问题的正确方法。 构造函数与工厂方法

请参阅 /amzn/click/com/0321246756" rel="nofollow noreferrer">框架设计指南:可重用 .NET 的约定、习惯用法和模式图书馆

5.3 构造函数设计

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

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

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

例如,列表构造函数 (Int32),抛出 ArgumentOutOfRangeException 当列表的容量参数为负数时。

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

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

更多信息

From Microsoft Constructor Design Guidelines (MSDN),

Do throw exceptions from instance constructors if appropriate.

Constructors should throw and handle exceptions like any method. Specifically, a constructor should not catch and hide any exceptions that it cannot handle.

Factory Method is not the right way to approach this problem. See Constructors vs Factory Methods

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

5.3 Constructor Design

Consider using a static factory method instead of a constructor if the
semantics of the desired operation do not map directly to the construction
of a new instance, or if following the constructor design guidelines
feels unnatural.

Do throw exceptions from instance constructors if appropriate.

.NET BCL implementations do throw exceptions from constructors

For example, the List Constructor (Int32), throws an ArgumentOutOfRangeException when the capacity argument of the list is negative.

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

Similarly, your constructor should throw an appropriate type of exception when it reads the file. For example, it could throw FileNotFoundException if the file does not exist at the specified location, etc.

More Information

他夏了夏天 2024-11-21 04:44:19

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

很多人[编辑]其中包括马丁,正如我在他的回答中读到的那样:)[/编辑]认为保持构造函数干净和小是件好事。我对此不太确定。如果你的对象没有这些数据就没有用,你也可以在构造函数中读入数据。如果您想构造对象,设置一些选项,然后读取数据(特别是在读取失败时可以重试),那么单独的方法也可以。所以选项2也是一个很好的可能性。也许更好,主要取决于口味。

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

All these solutions work, but as you said, C# doesn't allow to return null from a constructor. You either get an object or an exception. Since this is the C# way to go, I wouldn't choose option 3, because that merely mimics that other language you're talking about.

Lots of people [edit] among which is Martin, as I read in his answer :) [/edit] think it is good to keep your constructor clean and small. I'm not so sure about that. If your object is of no use without that data, you could read in the data in the constructor too. If you want to construct the object, set some options, and then read the data (especially with the possility to try again if the read fails), a separate method would be fine as well. So option 2 is a good possibility too. Even better maybe, depending mainly on taste.

So as long as you don't choose 3, choose the one you're the most comfortable with. :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文