一些简化字符串和数据存储操作的扩展方法





5.00/5 (2投票s)
这是一些我编写的扩展方法,用于简化字符串和数据存储的操作,并使我的代码更具可读性和表达力。
引言
当我开始编写大量数据服务代码并大量使用 Linq-to-SQL 时,我发现有必要通过为我发现笨拙的直接基本形式的代码片段创建包装器来简化我的代码。在这篇文章中,我分享了其中一些增强功能,希望对其他人有所帮助。
背景
使用 TryParse
模式(此处描述)有充分的理由。(我指的是在调用 .NET Framework 类型以及可能的其他类型的 TryParse
方法时“使用”它。)使用 TryParse
使我们能够避免使用“try
/parse
/catch
”反模式。(请注意,我不将 try
/catch
称为反模式!)尽管如此,人们可能希望 TryParse
模式的两个输出可以通过在输入字符串为 null
、空或格式无效时简单地返回默认值来减少为一个。这种简化在许多情况下都有效。这是一个例子:
// Where input is a string...
int count = input.ParsedValueOrDefault();
我们还来考虑使用 TryParse
模式的等效代码:
int count;
int.TryParse(input, out count);
比较这两个示例
ParsedValueOrDefault |
TryParse |
|
代码行数 | 1 | 2 |
---|---|---|
字符 | 38(不包括空格) | 38(不包括空格) |
输入有效 | count 是从输入字符串解析的值。 |
count 是从输入字符串解析的值。 |
输入无效 | count == 0 |
count == 0 |
很容易看出这两个示例在功能上是等效的,并且在首选单行代码的情况下使用 ParsedValueOrDefault
更好——例如,在使用 Linq-to-SQL 从数据源检索数据并塑造输出时,如下面的代码所示:
// Get from the data store all employees who have been
// with the company for more than 2 years.
resultList =
for item in Employees
where DateOfHire < DateTime.Now.AddYears(-2)
select new Employee
{
Salary = Salary.ParsedValueOrDefault(),
...
}).ToList();
诚然,最好不要将货币值存储为 string
——但我们无法始终控制数据存储的格式。
除了找到一种更简洁、在许多情况下都有用的方法来处理表示为 string
的基本类型和结构体(例如 DateTime
或 TimeSpan
)类型的值之外,我还开发了有助于确保返回给服务客户端的 string
永远不会是 null
的方法,即使它们在数据存储中被保存为 null
。(我的团队成员的共识是,这有助于防止错误并实现代码的某种简化。缺点是必须谨慎确保所需的条件。)当然,这种转换必须是可逆的。因此,EmptyIfNull
、EmptyIfNullTrimmed
、NullIfEmpty
和 NullIfEmptyTrimmed
方法应运而生。最后,为了补充 .NET Framework 的 GetValueOrDefault
方法,我需要一种方便的方法来将默认值转换为 null
以便存储,而 NullIfDefault
方法解决了这个问题。
除了 ParsedValueOrDefault
之外,所有这些方法都非常简单——只不过是条件表达式的包装器。对我来说,编写一次并频繁调用一个名称良好的方法,而不是频繁编写(相对更晦涩的)条件表达式,是值得的。
Using the Code
可下载的示例代码包含了下面总结的所有方法,并附带了每个方法的文档以及测试每个方法的测试代码。
下表提供了一个概述:
签名 | 描述 |
T ParsedValueOrDefault<T>(this string subject, params object[] optionalArguments) |
使用 TryParse 方法将 string 转换为类型 T 的值。有关输入 string 的信息(TryParse 的直接返回值)被丢弃,如果输入字符串为 null 、空或无效,则返回 default(T )。仅适用于实现 TryParse 模式的值类型,即 bool 、byte 、char 、DateTime 、DateTimeOffset 、decimal 、double 、float 、int 、long 、sbyte 、short 、TimeSpan 、uint 、ulong 和 ushort 。optionalArguments 是根据类型参数可能有效且有用的附加参数。
TimeSpan 一起使用),如果提供了两个参数,则它们的提供顺序无关紧要。仅当提供了 formatProvider 且非 null 时,才会应用该参数(或参数)。 |
string EmptyIfNull(this string subject) |
获取指定的 string ,如果它是 null ,则转换为 string.Empty 。 |
string EmptyIfNullTrimmed(this string subject) |
获取指定的 string ,如果它不是 null 或空,则进行修剪,如果它是 null ,则转换为 string.Empty 。 |
Nullable<T> NullIfDefault<T>(this T subject) where T : struct |
将值类型参数转换为可空值类型结果。如果参数等于其类型的默认值,则结果为 null 。(此方法的实现仅针对选定类型完成:bool 、byte 、char 和 DateTime 。) |
string NullIfEmpty(this string subject) |
如果参数 string 为 null 或空,则返回一个 null 的 string ;否则,返回参数 string 。 |
string NullIfEmptyTrimmed(this string subject) |
如果参数 string 为 null 或空,则返回一个 null 的 string ;否则,返回修剪后的参数 string 。 |
关注点
在查看可下载的代码时,读者会注意到 ParsedValueOrDefault
包装了另一个方法 ConvertToNullable
。起初,很长一段时间,我都很乐意在我的代码中使用 ConvertToNullable().GetValueOrDefault()
。我曾认为设计 ConvertToNullable
以提供 TryParse
的所有功能——包括确定输入字符串是否实际是类型 T
的值的有效表示的能力——是明智的。但是,除了 GetValueOrDefault
之外,我避免使用 ConvertToNullable
。为什么?当我们想要保留和使用有关输入 string
的信息时,让我们比较一下使用 ConvertToNullable
与 TryParse
。
int? countOrNull = input.ConvertToNullable();
if (countOrNull.HasValue)
{
int count = countOrNull.Value;
... // Do something with count.
}
int count;
if (int.TryParse(input, out count))
{
... // Do something with count.
}
显然,在这种情况下 TryParse
更受欢迎,如果一个人坚持使用 ConvertToNullable
,那将是一种反模式!既然以这种方式使用 ConvertToNullable
没有意义,那么就没有理由继续使用一个保留(输入字符串是否成功解析)TryParse
直接返回值的信息的方法。因此,ParsedValueOrDefault
应运而生,我计划今后调用它,而不是使用更笨拙的 ConvertToNullable().GetValueOrDefault()
调用。
下一步将通过消除 ParsedValueOrDefault
对 ConvertToNullable
的依赖来重构它以提高性能,因为现在很清楚,Nullable<T>
实例不应该以任何特定原因出现。然而,无论好坏,只要我的遗留代码继续使用 ConvertToNullable
,它就必须作为 public
方法保留在我的库中。在可下载的代码中,我将 ConvertToNullable
保持为 public
,并用 ObsoleteAttribute
实例对其进行修饰,这反映了它在我库中的状态。如果你想实际使用这些方法,我建议你删除该属性并将 ConvertToNullable
设为非公共。
关于 ConvertToNullable
的这些见解,直到我决定写这篇文章时才变得清晰——这篇文章“第无数次”地强化了一个我在职业生涯早期学到的观点:用自然语言记录和解释代码非常有价值——我认为,如果能找到时间来完成——它是高质量代码开发过程中必不可少的一步,因为它往往会暴露在“以代码为中心”模式下并非显而易见的重要事实。
历史
- 2012 年 1 月 16 日:首次发布