是否可以将以下'foreach'编写为LINQ语句,我想更常见的问题是,任何for循环都可以用LINQ语句替换。
我对任何潜在的性能成本都不感兴趣,只是在传统的命令式代码中使用声明性方法的潜力。
private static string SomeMethod()
{
if (ListOfResources .Count == 0)
return string.Empty;
var sb = new StringBuilder();
foreach (var resource in ListOfResources )
{
if (sb.Length != 0)
sb.Append(", ");
sb.Append(resource.Id);
}
return sb.ToString();
}
干杯
AWC
答案 0 :(得分:25)
不确定。哎呀,你可以用LINQ查询替换算术:
http://blogs.msdn.com/ericlippert/archive/2009/12/07/query-transformations-are-syntactic.aspx
但你不应该。
查询表达式的目的是表示查询操作。 “for”循环的目的是迭代特定语句,以便多次执行其副作用。这些往往是非常不同的。我鼓励更换循环,其目的仅仅是使用更高级别的结构查询数据,以便更清楚地查询数据。我强烈反对用查询理解替换产生副作用的代码,尽管这样做是可能的。
答案 1 :(得分:15)
一般来说是的,但是有些特殊情况非常困难。例如,一般情况下的以下代码在没有大量黑客攻击的情况下不会移植到LINQ表达式。
var list = new List<Func<int>>();
foreach ( var cur in (new int[] {1,2,3})) {
list.Add(() => cur);
}
之所以使用for循环,可以看到在闭包中如何捕获迭代变量的副作用。 LINQ表达式隐藏迭代变量的生命周期语义,并防止您看到捕获它的值的副作用。
请注意。上面的代码不等效于以下LINQ表达式。
var list = Enumerable.Range(1,3).Select(x => () => x).ToList();
foreach示例生成一个Func<int>
个对象列表,它们都返回3. LINQ版本生成一个Func<int>
列表,分别返回1,2和3。这使得这种捕获方式难以移植。
答案 2 :(得分:3)
实际上,您的代码执行的操作基本上是非常功能,即通过连接列表项将字符串列表减少为单个字符串。关于代码的唯一必要条件是使用StringBuilder
。
实际上,功能代码使这更容易,因为它不需要像代码那样的特殊情况。更好的是,.NET已经 实现了这个特定的操作,并且可能比你的代码 1)更有效:
return String.Join(", ", ListOfResources.Select(s => s.Id.ToString()).ToArray());
(是的,对ToArray()
的调用很烦人,但Join
是一种非常古老的方法,早于LINQ。)
当然,Join
的“更好”版本可以这样使用:
return ListOfResources.Select(s => s.Id).Join(", ");
实现相当简单 - 但再一次,使用StringBuilder
(性能)使其成为当务之急。
public static String Join<T>(this IEnumerable<T> items, String delimiter) {
if (items == null)
throw new ArgumentNullException("items");
if (delimiter == null)
throw new ArgumentNullException("delimiter");
var strings = items.Select(item => item.ToString()).ToList();
if (strings.Count == 0)
return string.Empty;
int length = strings.Sum(str => str.Length) +
delimiter.Length * (strings.Count - 1);
var result = new StringBuilder(length);
bool first = true;
foreach (string str in strings) {
if (first)
first = false;
else
result.Append(delimiter);
result.Append(str);
}
return result.ToString();
}
1)如果没有查看反射器中的实现,我猜测String.Join
首先通过字符串来确定总长度。这可以用于相应地初始化StringBuilder
,从而节省了昂贵的复制操作。
由SLaks编辑:以下是来自.Net 3.5的String.Join
相关部分的参考来源:
string jointString = FastAllocateString( jointLength );
fixed (char * pointerToJointString = &jointString.m_firstChar) {
UnSafeCharBuffer charBuffer = new UnSafeCharBuffer( pointerToJointString, jointLength);
// Append the first string first and then append each following string prefixed by the separator.
charBuffer.AppendString( value[startIndex] );
for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++) {
charBuffer.AppendString( separator );
charBuffer.AppendString( value[stringToJoinIndex] );
}
BCLDebug.Assert(*(pointerToJointString + charBuffer.Length) == '\0', "String must be null-terminated!");
}
答案 3 :(得分:2)
技术上,是的。
任何foreach
循环都可以使用ForEach
扩展方法转换为LINQ,例如MoreLinq中的扩展方法。
如果您只想使用“纯”LINQ(仅限内置扩展方法),则可以滥用Aggregate
扩展方法,如下所示:
foreach(type item in collection { statements }
type item;
collection.Aggregate(true, (j, itemTemp) => {
item = itemTemp;
statements
return true;
);
这将正确处理任何 foreach循环,甚至是JaredPar的答案。 编辑:除非它使用ref
/ out
参数,不安全代码或yield return
。
你不敢在实际代码中使用这个技巧。
在您的特定情况下,您应该使用字符串Join
扩展方法,例如:
///<summary>Appends a list of strings to a StringBuilder, separated by a separator string.</summary>
///<param name="builder">The StringBuilder to append to.</param>
///<param name="strings">The strings to append.</param>
///<param name="separator">A string to append between the strings.</param>
public static StringBuilder AppendJoin(this StringBuilder builder, IEnumerable<string> strings, string separator) {
if (builder == null) throw new ArgumentNullException("builder");
if (strings == null) throw new ArgumentNullException("strings");
if (separator == null) throw new ArgumentNullException("separator");
bool first = true;
foreach (var str in strings) {
if (first)
first = false;
else
builder.Append(separator);
builder.Append(str);
}
return builder;
}
///<summary>Combines a collection of strings into a single string.</summary>
public static string Join<T>(this IEnumerable<T> strings, string separator, Func<T, string> selector) { return strings.Select(selector).Join(separator); }
///<summary>Combines a collection of strings into a single string.</summary>
public static string Join(this IEnumerable<string> strings, string separator) { return new StringBuilder().AppendJoin(strings, separator).ToString(); }
答案 4 :(得分:2)
您的问题中的特定循环可以以声明方式完成,如下所示:
var result = ListOfResources
.Select<Resource, string>(r => r.Id.ToString())
.Aggregate<string, StringBuilder>(new StringBuilder(), (sb, s) => sb.Append(sb.Length > 0 ? ", " : String.Empty).Append(s))
.ToString();
至于性能,您可以预期性能下降,但这对于大多数应用程序来说都是可以接受的。
答案 5 :(得分:2)
我认为最重要的是,为避免语义混淆,当实际功能时,您的代码应该只是表面功能。换句话说,请不要在LINQ表达式中使用副作用。
答案 6 :(得分:1)
通常,您可以使用委托来编写lambda表达式,该委托表示foreach循环的主体,在您的情况下类似于:
resource => { if (sb.Length != 0) sb.Append(", "); sb.Append(resource.Id); }
然后只需在ForEach扩展方法中使用。这是否是一个好主意取决于身体的复杂性,如果它太大而复杂,你可能不会从中获得任何东西,除非可能产生混淆;)