Can all 'for' loops be replaced with a LINQ statement?

前端 未结 7 546
逝去的感伤
逝去的感伤 2021-02-05 08:48

Is it possible to write the following \'foreach\' as a LINQ statement, and I guess the more general question can any for loop be replaced by a LINQ statement.

I\'m not i

7条回答
  •  广开言路
    2021-02-05 09:06

    In fact, your code does something which is fundamentally very functional, namely it reduces a list of strings to a single string by concatenating the list items. The only imperative thing about the code is the use of a StringBuilder.

    The functional code makes this much easier, actually, because it doesn’t require a special case like your code does. Better still, .NET already has this particular operation implemented, and probably more efficient than your code1):

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

    (Yes, the call to ToArray() is annoying but Join is a very old method and predates LINQ.)

    Of course, a “better” version of Join could be used like this:

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

    The implementation is rather straightforward – but once again, using the StringBuilder (for performance) makes it imperative.

    public static String Join(this IEnumerable 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) Without having looked at the implementation in the reflector, I’d guess that String.Join makes a first pass over the strings to determine the overall length. This can be used to initialize the StringBuilder accordingly, thus saving expensive copy operations later on.

    EDIT by SLaks: Here is the reference source for the relevant part of String.Join from .Net 3.5:

    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!");
    } 
    

提交回复
热议问题