处理C#构造函数中损坏的输入数据最合适的方法是什么
本文关键字:是什么 方法 数据 输入 构造函数 损坏 处理 | 更新日期: 2023-09-27 17:58:14
我正在从文件中读取数据,并基于这些数据创建对象。数据格式不在我的控制之下,偶尔会损坏。在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;
}
我会按照选项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;
}
我不会在构造函数中放入任何可能引发异常的内容,除非出现真正的错误
如果您的构造函数有一个可能的返回值,而不是一个有效的对象,您应该封装它。
最安全的方法可能是创建一个工厂方法(类中的公共静态函数,该函数接受文件引用并返回该类的新实例或null)。此方法应首先验证文件及其数据,然后再创建一个新对象。
如果文件数据的结构很简单,可以先将其加载到某个局部变量中,然后用这些数据构造对象。否则,您仍然可以在工厂方法内部决定是否要尝试/捕获构造或使用上面提到的任何其他点。
选项#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
}
}
来自Microsoft Constructor Design Guidelines(MSDN),
如果合适,请从实例构造函数中抛出异常。
构造函数应该像任何方法一样抛出和处理异常。具体来说,构造函数不应该捕获和隐藏它无法处理的任何异常。
工厂方法不是解决此问题的正确方法。参见施工人员与工厂方法
来自框架设计指南:可重用.NET库的惯例、习语和模式
5.3建造商设计
如果所需操作的语义不会直接映射到构造新实例的,或者如果遵循构造函数设计指南感觉不自然。
如果合适,请从实例构造函数中抛出异常。
.NET BCL实现确实从构造函数抛出异常
例如,列表构造函数(Int32)在列表的容量参数为负数时抛出ArgumentOutOfRangeException。
var myList = new List<int>(-1); // throws ArgumentOutOfRangeException
类似地,构造函数在读取文件时应该抛出适当类型的异常。例如,如果文件在指定位置不存在,它可能会抛出FileNotFoundException,等等。
更多信息
- 代码合同
- 从.Net中的构造函数引发异常
- 在构造函数中引发ArgumentNullException
- C#中的构造函数参数验证-最佳实践
所有这些解决方案都有效,但正如您所说,C#不允许从构造函数返回null。您要么得到对象,要么得到异常。由于这是C#的发展方向,我不会选择选项3,因为这只是模仿了你正在谈论的其他语言。
很多人[edit],其中包括Martin,正如我在他的回答中所读到的:)[/edit]认为保持构造函数干净小巧是件好事。我对此不太确定。如果没有这些数据,您的对象就没有用处,那么您也可以在构造函数中读取数据。如果你想构造对象,设置一些选项,然后读取数据(尤其是在读取失败时可以重试),那么单独的方法也可以。因此,选择2也是一个很好的可能性。也许更好,主要取决于口味。
所以,只要你不选择3个,就选择你最舒服的一个。:)