在编程中,循环是一个基本且频繁使用的构造。然而,大多数情况下,只是停留在使用标准的for、foreach、while循环。实际上,可以做得更智能。在许多情况下,会在循环中添加额外的信息,例如检查索引是偶数还是奇数,或者检查当前值是否是第一个或最后一个。
更智能的循环
非常喜欢lambda表达式——事实上,可能有点过于喜欢了。能够像参数一样传递委托确实可能会被滥用,但在某些情况下,它可以使代码更加优雅。让来看一些可以改进数组操作的代码。
以下是一个C#的扩展方法示例,它提供了一种在循环中使用额外信息的方式:
namespace LoopExtensions {
public static class LoopExtensionMethods {
public static IEnumerable Each(this IEnumerable collection, Action> each) {
return LoopExtensionMethods.Each(collection, 0, collection.Count(), each);
}
public static IEnumerable Each(this IEnumerable collection, int start, Action> each) {
return LoopExtensionMethods.Each(collection, start, collection.Count(), each);
}
public static IEnumerable Each(this IEnumerable collection, int start, int end, Action> each) {
Action, T> handle = (detail, item) => { each(detail); };
return LoopExtensionMethods.Each(collection, start, end, handle);
}
public static IEnumerable Each(this IEnumerable collection, Action, T> each) {
return LoopExtensionMethods.Each(collection, 0, collection.Count(), each);
}
public static IEnumerable Each(this IEnumerable collection, int start, Action, T> each) {
return LoopExtensionMethods.Each(collection, start, collection.Count(), each);
}
public static IEnumerable Each(this IEnumerable collection, int start, int end, Action, T> each) {
if (start < 0 || end > collection.Count()) {
throw new ArgumentOutOfRangeException();
}
foreach (T value in collection) {
each(new ElementDetail(value, start++, end, collection), value);
if (start == end) {
break;
}
}
return collection;
}
}
public class ElementDetail {
internal ElementDetail(T value, int index, int total, IEnumerable collection) {
this.Value = value;
this.Index = index;
this.Total = total;
this.Collection = collection;
}
public int Index { get; private set; }
public int Total { get; private set; }
public T Value { get; private set; }
public IEnumerable Collection { get; private set; }
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;
}
}
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;
}
}
}
}
这段代码虽然很长,但它是一组扩展方法,可以与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);
});
输出:
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代码,并决定哪一段更容易阅读:
ASP.NET
<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>
或者使用循环助手的相同代码…
ASP.NET
<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类,但当然,也可以使用其他扩展方法,它还单独提供值。
为人类编写代码,而不是为计算机