所有的“for”循环都可以用LINQ语句替换吗?

xzabzqsa  于 2024-01-03  发布在  其他
关注(0)|答案(7)|浏览(200)

是否可以将下面的'foreach'写为LINQ语句,我想更一般的问题是任何for循环都可以被LINQ语句取代。
我对任何潜在的性能成本都不感兴趣,我只对在传统的命令式代码中使用声明性方法的可能性感兴趣。

  1. private static string SomeMethod()
  2. {
  3. if (ListOfResources .Count == 0)
  4. return string.Empty;
  5. var sb = new StringBuilder();
  6. foreach (var resource in ListOfResources )
  7. {
  8. if (sb.Length != 0)
  9. sb.Append(", ");
  10. sb.Append(resource.Id);
  11. }
  12. return sb.ToString();
  13. }

字符串
欢呼
AWC

0tdrvxhp

0tdrvxhp1#

当然。见鬼,但你不应该。
查询表达式的目的是 * 表示一个查询操作 *。“for”循环的目的是遍历一个特定的语句,以便多次执行其副作用。这些经常是非常不同的。我鼓励用更高级别的结构来替换循环,这些结构更清楚地查询数据。我强烈反对替换副作用-生成带有查询解析的代码,尽管这样做是可能的。

9vw9lbht

9vw9lbht2#

一般来说是的,但也有一些特殊的情况是非常困难的。例如,下面的代码在一般情况下不移植到LINQ表达式,除非有大量的黑客攻击。

  1. var list = new List<Func<int>>();
  2. foreach ( var cur in (new int[] {1,2,3})) {
  3. list.Add(() => cur);
  4. }

字符串
原因是使用for循环,可以看到如何在闭包中捕获迭代变量的副作用。LINQ表达式隐藏了迭代变量的生存期语义,并防止您看到捕获其值的副作用。
注意:上面的代码等价于下面的LINQ表达式。

  1. var list = Enumerable.Range(1,3).Select(x => () => x).ToList();


foreach示例生成一个Func<int>对象列表,所有对象都返回3。LINQ版本生成一个Func<int>列表,它们分别返回1、2和3。这就是这种捕获风格难以移植的原因。

5jdjgkvh

5jdjgkvh3#

我认为这里最重要的是,为了避免语义上的混乱,你的代码应该只在它实际上是功能性的时候才是功能性的。换句话说,请不要在LINQ表达式中使用副作用。

l7mqbcuq

l7mqbcuq4#

事实上,你的代码做了一些基本上非常实用的事情,即通过连接列表项将字符串列表减少到一个字符串。关于代码的唯一必要的事情是使用StringBuilder
实际上,函数式代码使这变得更容易,因为它不需要像你的代码那样特殊的情况。更好的是,.NET已经实现了这个特定的操作,并且可能比你的代码更有效1):

  1. return String.Join(", ", ListOfResources.Select(s => s.Id.ToString()).ToArray());

字符串
(Yes,调用ToArray()很烦人,但Join是一个非常古老的方法,早于LINQ。
当然,Join的“更好”版本可以这样使用:

  1. return ListOfResources.Select(s => s.Id).Join(", ");


这个实现相当简单--但是再一次,使用StringBuilder(为了性能)使它变得势在必行。

  1. public static String Join<T>(this IEnumerable<T> items, String delimiter) {
  2. if (items == null)
  3. throw new ArgumentNullException("items");
  4. if (delimiter == null)
  5. throw new ArgumentNullException("delimiter");
  6. var strings = items.Select(item => item.ToString()).ToList();
  7. if (strings.Count == 0)
  8. return string.Empty;
  9. int length = strings.Sum(str => str.Length) +
  10. delimiter.Length * (strings.Count - 1);
  11. var result = new StringBuilder(length);
  12. bool first = true;
  13. foreach (string str in strings) {
  14. if (first)
  15. first = false;
  16. else
  17. result.Append(delimiter);
  18. result.Append(str);
  19. }
  20. return result.ToString();
  21. }


1)在没有查看反射器中的实现的情况下,我猜String.Join首先遍历字符串以确定总长度。这可以用于相应地初始化StringBuilder,从而节省稍后昂贵的复制操作。

EDIT by Slaks:以下是来自.Net 3.5的String.Join相关部分的参考源代码:

  1. string jointString = FastAllocateString( jointLength );
  2. fixed (char * pointerToJointString = &jointString.m_firstChar) {
  3. UnSafeCharBuffer charBuffer = new UnSafeCharBuffer( pointerToJointString, jointLength);
  4. // Append the first string first and then append each following string prefixed by the separator.
  5. charBuffer.AppendString( value[startIndex] );
  6. for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++) {
  7. charBuffer.AppendString( separator );
  8. charBuffer.AppendString( value[stringToJoinIndex] );
  9. }
  10. BCLDebug.Assert(*(pointerToJointString + charBuffer.Length) == '\0', "String must be null-terminated!");
  11. }

展开查看全部
xytpbqjk

xytpbqjk5#

你的问题中的特定循环可以像这样声明地完成:

  1. var result = ListOfResources
  2. .Select<Resource, string>(r => r.Id.ToString())
  3. .Aggregate<string, StringBuilder>(new StringBuilder(), (sb, s) => sb.Append(sb.Length > 0 ? ", " : String.Empty).Append(s))
  4. .ToString();

字符串
至于性能,您可以预期性能下降,但这对大多数应用程序来说是可以接受的。

9o685dep

9o685dep6#

严格来说是的
任何foreach循环都可以通过使用ForEach扩展方法转换为LINQ,例如MoreLinq中的方法。
如果你只想使用“纯”LINQ(只使用内置的扩展方法),你可以滥用Aggregate扩展方法,如下所示:

  1. foreach(type item in collection { statements }
  2. type item;
  3. collection.Aggregate(true, (j, itemTemp) => {
  4. item = itemTemp;
  5. statements
  6. return true;
  7. );

字符串
这将正确处理 * 任何 * foreach循环,甚至JaredPar的答案。EDIT:除非它使用ref/out参数,不安全代码或yield return
不要在真实的代码中使用这个技巧。
在您的特定情况下,您应该使用字符串Join扩展方法,例如:

  1. ///<summary>Appends a list of strings to a StringBuilder, separated by a separator string.</summary>
  2. ///<param name="builder">The StringBuilder to append to.</param>
  3. ///<param name="strings">The strings to append.</param>
  4. ///<param name="separator">A string to append between the strings.</param>
  5. public static StringBuilder AppendJoin(this StringBuilder builder, IEnumerable<string> strings, string separator) {
  6. if (builder == null) throw new ArgumentNullException("builder");
  7. if (strings == null) throw new ArgumentNullException("strings");
  8. if (separator == null) throw new ArgumentNullException("separator");
  9. bool first = true;
  10. foreach (var str in strings) {
  11. if (first)
  12. first = false;
  13. else
  14. builder.Append(separator);
  15. builder.Append(str);
  16. }
  17. return builder;
  18. }
  19. ///<summary>Combines a collection of strings into a single string.</summary>
  20. public static string Join<T>(this IEnumerable<T> strings, string separator, Func<T, string> selector) { return strings.Select(selector).Join(separator); }
  21. ///<summary>Combines a collection of strings into a single string.</summary>
  22. public static string Join(this IEnumerable<string> strings, string separator) { return new StringBuilder().AppendJoin(strings, separator).ToString(); }

展开查看全部
w51jfk4q

w51jfk4q7#

一般来说,你可以使用一个代表foreach循环主体的委托来编写一个lambda表达式,在你的例子中,类似于:

  1. resource => { if (sb.Length != 0) sb.Append(", "); sb.Append(resource.Id); }

字符串
然后简单地在ForEach扩展方法中使用。这是否是一个好主意取决于主体的复杂性,如果它太大太复杂,除了可能的混乱之外,你可能不会从中获得任何东西;)

相关问题