有趣的C#本地函数
许多流行的语言都支持使用本地函数,并且在C#7中,对它们的支持几乎没有大张旗鼓地宣布。作为一个自认为是C#超级用户的人,我很少利用该功能,直到我意识到它对提高代码可读性有多大帮助,特别是在上下文中,它代替了注释/黑客,单元测试和一般来说,只是为了清理东西。
究竟什么是本地函数?
本地函数是嵌套在另一个成员中的一种private类型的方法。只能从其包含成员中调用它们。可以在以下位置声明和调用本地函数:
- 方法,尤其是迭代器方法和异步方法
- 构造函数
- 属性访问器
- 事件访问器
- 匿名方法
- Lambda表达式
- Finalizers
与大多数情况一样,有时,仅_向_您_显示本地_函数的外观更容易:
public static IEnumerable<Address> SanitizeAddresses(List<Address> addresses)
{
foreach(Address address in addresses)
{
yield return Anonymize(address);
}
// This is a local function
Address Anonymize(Address address) { ... }
}
使用本地函数清理注释
想到的第一个可以帮助缓解功能的用例之一是任何令人讨厌的环境卫生或业务逻辑规则,尤其是关于字符串操作的规则。如果您在足够多的业务应用程序中工作,您无疑会发现一些令人讨厌的事情。关于_为什么_要这样做的大量注释:
public static User ProcessUser(User user)
{
// All names must conform to some crazy Dr. Seuss-eqsue rhyming scheme
// along with every other character being placed by its closest numerically
// shaped equivalent
var seussifyExpression = new Regex("...");
user.Name = seussifyExpression.Replace(user.Name, "...");
user.Name = user.Name
.Replace(..., ...)
.Replace(..., ...)
.Replace(..., ...);
// Other processes omitted for brevity
return user;
}
如您在这里看到的,我们有一系列的链式替换,其中一些依赖于字符串,而另一些依赖于正则表达式,这会使方法变得笨拙,尤其是当要执行多个操作时。现在,您可以在此处定义一个本地函数来封装所有这些业务逻辑以替换您的疯狂注释:
public static User ProcessUser(User user)
{
SanitizeName(user)
// Other processes omitted for brevity
return user;
void SanitizeName(User user)
{
var seussifyExpression = new Regex("...");
user.Name = seussifyExpression.Replace(user.Name, "...");
user.Name = user.Name
.Replace(..., ...)
.Replace(..., ...)
.Replace(..., ...);
return user;
}
}
你可以随意命名你的本地函数,甚至ApplyBusinessLogicNamingRules(),并包含你想要的任何必要的注释(如果你确实需要回答你为什么要做某事的话),但这应该有助于代码的其余部分告诉您它在做什么,而无需显式地编写注释。
借助本地函数全力以赴阅读Rainbow
如果可读性不是关于代码的最重要的事情,那么它就在顶部附近。
LINQ是本地函数可以协助的另一个流行领域,尤其是如果您必须对一系列记录执行任何类型的疯狂过滤逻辑时。您可以定义一系列本地函数,这些函数可以覆盖过滤过程(或实际上任何过程)的每个步骤,并且从可读性的角度更轻松地推论代码:
public List<int> FindPrimesStartingWithASpecificLetter(List<int> numbers, int startingDigit)
{
return numbers.Where(n => n > 1 && Enumerable.Range(1, n).Where
(x => n % x == 0).SequenceEqual(new [] {1, n }))
.Where(n => $"{n}".StartsWith($"{startingDigit}"));
}
虽然简洁,但_阅读_效果并不理想。让我们来看看它在接触一些本地函数之后的样子:
public List<int> FindPrimesStartingWithASpecificLetter(List<int> numbers, int startingDigit)
{
return numbers.Where(x => IsPrime(x) && StartsWithDigit(x, startingDigit));
bool IsPrime(int n) => return n > 1 && Enumerable.Range(1, n).
Where(x -> n % n == 0).SequenceEqual(new [] { 1, n }));
bool StartsWithDigit(int n, int startingDigit) => return $"{n}".StartsWith
($"{startingDigit}");
}
如您所见,本地函数正在帮助将所有丑陋/讨厌的逻辑包装在它们自己的微小函数中。这是一个非常琐碎的情况,但是正如您可能想像的那样,如果您的代码行没有碰到一种方法之外的任何东西,那么它很可能是本地函数的可靠候选者。
测试,测试,本地的!
如果您花费了很多时间来编写单元或集成测试,则您可能熟悉寓言中的“Arrange-Act-Assert”模式,该模式用于在测试给定代码段时将每个功能分开,例如:如下:
- 安排所有必要的前提条件和投入。
- 对被测对象或方法采取行动。
- 断言已经发生了预期的结果。
正如您可能想象的那样,对于复杂的测试用例,这可能非常适合这种模式:
public void IsThisAnArrangeActAssertLocalFunction()
{
Arrange();
Act();
Assert();
void Arrange() { ... }
void Act() { ... }
void Assert() { ... }
}
实用吗?它适合所有用例吗?您会发现自己使用过的东西吗?所有这些答案都可能是压倒性的,但似乎确实是本地函数_可以_发挥作用的场景。
选择自己的冒险
本地函数提供了一些有趣的选项,这些选项比其他情况更适合某些情况。绝对可以代替大型注释或非常混乱的业务逻辑。在单元测试或很少的衬里中——可能没有。拥有大多数新特征,尤其是那些含糖的功能,这取决于您和您的团队是否真正为您工作。虽然它们在某些情况下看起来很吸引人,但它们也似乎已经成熟,可以被滥用,可能是混乱的方法,以及其他问题,这些问题可能会完全挫败最初使用它们的目的。
本文转自 link,如有侵权,请联系删除。