使用 C# 构建更智能的循环





5.00/5 (1投票)
循环是任何程序员工具箱中的标准工具。但它们经常出现在我们的代码中,不得不让人思考为什么没有看到更多关于如何改进它们的使用方法。本文讨论了一些通过使用扩展方法来改进循环的想法。
引言
每个程序员首先学习的事情之一就是如何循环遍历信息数组。我们学习如何使用 for
、foreach
、while
循环,但之后我们很少对其进行改进。对于大多数情况来说,标准循环就是程序员所能达到的极限。
我认为循环应该比这更智能。事实上,我们经常编写使用循环进行额外信息处理的代码。在循环中检查索引是偶数还是奇数,或者值是第一个还是最后一个,这并不罕见。
稍微更智能的循环
我非常喜欢 lambda 表达式——事实上,可能太喜欢了。能够像参数一样传递委托肯定会被滥用,但在某些情况下,它可以编写出非常优雅的代码。让我们看一些可以改进数组处理的代码。
namespace LoopExtensions {
//Some comments removed for brevity...
#region Extension Methods
/// <summary>
/// Handles looping through collections with additional details
/// </summary>
public static class LoopExtensionMethods {
public static IEnumerable<T> Each<T>(this IEnumerable<T> collection,
Action<ElementDetail<T>> each) {
return LoopExtensionMethods.Each<T>(collection, 0, collection.Count(), each);
}
public static IEnumerable<T> Each<T>(this IEnumerable<T> collection,
int start,
Action<ElementDetail<T>> each) {
return LoopExtensionMethods.Each<T>(collection, start, collection.Count(), each);
}
public static IEnumerable<T> Each<T>(this IEnumerable<T> collection,
int start,
int end,
Action<ElementDetail<T>> each) {
Action<ElementDetail<T>, T> handle = (detail, item) => { each(detail); };
return LoopExtensionMethods.Each<T>(collection, start, end, handle);
}
public static IEnumerable<T> Each<T>(this IEnumerable<T> collection,
Action<ElementDetail<T>, T> each) {
return LoopExtensionMethods.Each<T>(collection, 0, collection.Count(), each);
}
public static IEnumerable<T> Each<T>(this IEnumerable<T> collection,
int start,
Action<ElementDetail<T>, T> each) {
return LoopExtensionMethods.Each<T>(collection, start, collection.Count(), each);
}
public static IEnumerable<T> Each<T>(this IEnumerable<T> collection,
int start,
int end,
Action<ElementDetail<T>, T> each) {
//verify the ranges
if (start < 0 || end > collection.Count()) {
throw new ArgumentOutOfRangeException();
}
//perform the work
foreach (T value in collection) {
each(new ElementDetail<T>(value, start++, end, collection), value);
if (start == end) { break; }
}
return collection;
}
}
#endregion
#region Detail Information Class
/// <summary>
/// Contains a summary of information about the item current in the loop
/// </summary>
public class ElementDetail<T> {
#region Constructors
internal ElementDetail(T value, int index, int total, IEnumerable<T> collection) {
this.Value = value;
this.Index = index;
this.Total = total;
this.Collection = collection;
}
#endregion
#region Loop Information
public int Index { get; private set; }
public int Total { get; private set; }
public T Value { get; private set; }
public IEnumerable<T> Collection { get; private set; }
#endregion
#region Value Properties
public T Previous {
get {
return !this.First
? this.Collection.ElementAt(this.Index - 1)
: default(T);
}
}
public T Next {
get {
return !this.Last
? this.Collection.ElementAt(this.Index + 1)
: default(T);
}
}
public bool Last {
get { return this.Index == (this.Total - 1); }
}
public bool First {
get { return this.Index == 0; }
}
public bool Outer {
get { return this.First || this.Last; }
}
public bool Inner {
get { return !this.Outer; }
}
public bool Even {
get { return this.Index % 2 == 0; }
}
public bool Odd {
get { return !this.Even; }
}
#endregion
#region Collection Properties
public int StepNumber {
get { return this.Index + 1; }
}
public float PercentCompleted {
get { return (((float)this.Index / (float)this.Total) * 100); }
}
public float PercentRemaining {
get { return 100 - this.PercentCompleted; }
}
public int StepsCompleted {
get { return this.Index; }
}
public int StepsRemaining {
get { return this.Total - this.Index; }
}
#endregion
}
#endregion
}
现在,这有很多代码,但如果你仔细查看,你会发现它是一组可以与 IEnumerable
一起使用的扩展方法。基本思想是,我们可以在一个委托中执行循环,该委托接受有关循环中元素的其他信息。
所以,这里有一个简单的例子…
string[] items = {
"Apple",
"Orange",
"Grape",
"Watermellon",
"Kiwi"
};
items.Each((item) => {
Console.Write("{0} > ", item.Inner ? "Inner" : "Outer");
if (!item.First) { Console.Write("Previous: {0}, ", item.Previous); }
Console.Write("Current: {0} ({1})", item.Value, item.StepNumber);
if (!item.Last) { Console.Write(", Next: {0}", item.Next); }
Console.WriteLine(" -- {0}% remaining", item.PercentRemaining);
});
// Output
// Outer > Current: Apple (1), Next: Orange -- 100% remaining
// Inner > Previous: Apple, Current: Orange (2), Next: Grape -- 80% remaining
// Inner > Previous: Orange, Current: Grape (3), Next: Watermelon -- 60% remaining
// Inner > Previous: Grape, Current: Watermellon (4), Next: Kiwi -- 40% remaining
// Outer > Previous: Watermelon, Current: Kiwi (5) -- 20% remaining
通常,你会简单地将所有比较放在循环内,然后根据需要使用它们。相反,在这段代码中,我们传入一个额外的参数,其中包含指向许多常见比较的快捷方式,这些比较可能出现在循环中。通过这样做,我们提高了可读性,并专注于循环正在做什么。
旧方法的问题
没有! 将比较写在行内可以具有优势,因为你不需要做比必要的工作更多。但是,在某些情况下,提高的可读性将对代码质量产生重大影响。考虑这两段 MVC 代码,并决定哪一段更容易阅读
<ul>
<% foreach(SiteMapNode node in breadcrumb) { %>
<li class="item <% =(breadcrumb.IndexOf(node) % 2 == 0 ? "item-even" : "item-odd") %>" >
<% if (breadcrumb.First().Equals(node) || breadcrumb.Last().Equals(node)) { %>
<strong>
<% } %>
<a href="<% =node.Url %>" >
(<% =(breadcrumb.IndexOf(node) + 1) %>) : <% =node.Text %>
</a>
<% if (breadcrumb.First().Equals(node) || breadcrumb.Last().Equals(node)) { %>
</strong>
<% } %>
</li>
<% } %>
</ul>
或者使用循环辅助工具的相同代码…
<ul>
<% breadcrumb.Each((node) => { %>
<li class="item <% =(node.Even ? "item-even" : "item-odd") %>" >
<% if (node.Outer) { %><strong><% } %>
<a href="<% =node.Value.Url %>" >
(<% = node.StepNumber %>) : <% =node.Value.Text %>
</a>
<% if (node.Outer) { %></strong><% } %>
</li>
<% }); %>
</ul>
这个例子展示了仅使用 ElementDetail
类,当然,你也可以使用同时提供值的其他扩展方法。
为人类编写代码,而不是为计算机编写代码
每个人都听说过这个建议——或多或少,可读性可能是你代码中最重要的部分。计算机可以轻松地读取这两个例子,而人类可能需要思考片刻才能确定代码的作用。
无论如何,这种简单而有效的方法可以将以前难以阅读的代码转化为编程艺术品……或者类似的东西…