我正在从文件中读取数据并根据此数据创建对象。数据格式不受我的控制,偶尔也会损坏。在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;
}
答案 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.TryParse
和Int32.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,选择你最舒服的那个。 :)