我正在从文件中读取数据并根据该数据创建对象。数据格式不受我的控制,并且偶尔会损坏。在 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;
}
发布评论
评论(5)
我会按照选项3)做一些事情:
然后像这样使用它:
一般来说,构造函数应该将本质上定义类的所有属性作为参数。进行重量级计算(例如解析文件)最好留给工厂方法,因为通常不希望构造函数执行复杂且可能长时间运行的任务。
更新:正如评论中提到的,更好的模式是这样的:
I would do something along the lines of option 3):
And then use it like this:
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:
我不会将任何可能引发异常的内容放入构造函数中 - 除非出现真正错误。
如果你的构造函数有一个可能的返回值而不是一个有效的对象,你应该封装它。
最安全的方法可能是创建一个工厂方法(在类中接受文件引用和返回类的新实例或 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.
选项 #1 和 #3 都是不错的选择,并且在 .Net 框架中很常见。为同一类型提供两者也很常见。考虑
Int32.TryParse
和Int32.Parse
。提供这两者可以为开发人员提供更多的灵活性,而不会损害类型的完整性。我强烈建议你避免#2。此模式强制类型作者和类型使用者处理处于多种状态的类型实例
这给每个使用者带来了处理处于所有不同状态的实例的负担(即使反应就是直接抛出)。此外,它还迫使消费者采用非标准模式。开发人员必须了解您的类型是特殊的,并且需要对其进行构造然后初始化。它违背了 .Net 中创建对象的标准方式。
注意#3,尽管我会采取一些不同的方法。异常形式应该按照try形式来实现。这是向用户提供这两个选项时的标准模式。考虑以下模式
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
andInt32.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
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
来自 Microsoft 构造函数设计指南 (MSDN),
工厂方法不是解决此问题的正确方法。 构造函数与工厂方法
请参阅 /amzn/click/com/0321246756" rel="nofollow noreferrer">框架设计指南:可重用 .NET 的约定、习惯用法和模式图书馆
.NET BCL 实现确实会从构造函数中抛出异常
例如,列表构造函数 (Int32),抛出 ArgumentOutOfRangeException 当列表的容量参数为负数时。
同样,构造函数在读取文件时应该抛出适当类型的异常。例如,如果文件指定位置不存在等
更多信息
From Microsoft Constructor Design Guidelines (MSDN),
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
.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.
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
所有这些解决方案都有效,但正如您所说,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. :)