Sử dụng LINQ để nối chuỗi


345

Cách hiệu quả nhất để viết trường học cũ là gì:

StringBuilder sb = new StringBuilder();
if (strings.Count > 0)
{
    foreach (string s in strings)
    {
        sb.Append(s + ", ");
    }
    sb.Remove(sb.Length - 2, 2);
}
return sb.ToString();

... trong LINQ?


1
Bạn đã khám phá bất kỳ cách làm LINQ siêu mát mẻ khác?
Robert S.

3
Vâng, câu trả lời được chọn và tất cả các tùy chọn khác không hoạt động trong Linq to Entities.
Binoj Antony

3
@Binoj Antony, đừng làm cho cơ sở dữ liệu của bạn thực hiện nối chuỗi.
Amy B

6
@ Pr0fess0rX: Bởi vì nó không thể và bởi vì nó không nên. Tôi không biết về các cơ sở dữ liệu khác nhưng trong SQL Server, bạn chỉ có thể concat (n) varcahr giới hạn bạn (n) varchar (max). Không nên vì logic nghiệp vụ không nên được triển khai trong lớp dữ liệu.
the_drow

bất kỳ giải pháp cuối cùng với mã nguồn đầy đủ và hiệu suất cao?
Kiquenet

Câu trả lời:


527

Câu trả lời này cho thấy việc sử dụng LINQ ( Aggregate) theo yêu cầu trong câu hỏi và không dành cho sử dụng hàng ngày. Bởi vì điều này không sử dụng, StringBuildernó sẽ có hiệu suất khủng khiếp cho các chuỗi rất dài. Đối với sử dụng mã thông thường String.Joinnhư trong câu trả lời khác

Sử dụng các truy vấn tổng hợp như thế này:

string[] words = { "one", "two", "three" };
var res = words.Aggregate(
   "", // start with empty string to handle empty list case.
   (current, next) => current + ", " + next);
Console.WriteLine(res);

Kết quả này:

, một hai ba

Tổng hợp là một hàm lấy một tập hợp các giá trị và trả về giá trị vô hướng. Các ví dụ từ T-SQL bao gồm min, max và sum. Cả VB và C # đều có hỗ trợ cho tổng hợp. Cả VB và C # đều hỗ trợ tổng hợp dưới dạng phương thức mở rộng. Sử dụng ký hiệu dấu chấm, người ta chỉ cần gọi một phương thức trên một đối tượng IEnumerable .

Hãy nhớ rằng các truy vấn tổng hợp được thực hiện ngay lập tức.

Thêm thông tin - MSDN: Truy vấn tổng hợp


Nếu bạn thực sự muốn sử Aggregatedụng biến thể sử dụng sử dụng StringBuilderđược đề xuất trong nhận xét của CodeMonkeyKing , đó sẽ là mã giống như thông thường String.Joinbao gồm hiệu năng tốt cho số lượng lớn đối tượng:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();

4
Ví dụ đầu tiên không xuất ra "một, hai, ba", nó xuất ra ", một, hai, ba" (Lưu ý dấu phẩy hàng đầu).
Mort

Trong ví dụ đầu tiên của bạn, vì bạn gieo hạt giống "", giá trị đầu tiên được sử dụng currentlà một chuỗi rỗng. Vì vậy, đối với 1 hoặc nhiều phần tử, bạn sẽ luôn nhận được , ở đầu chuỗi.
Michael Yanni

@Mort Tôi đã sửa cái này
sergtk

358
return string.Join(", ", strings.ToArray());

Trong .Net 4, có một sự quá tải mới cho string.Joinsự chấp nhận đó IEnumerable<string>. Mã này sẽ trông như sau:

return string.Join(", ", strings);

2
OK, vì vậy giải pháp không sử dụng Linq, nhưng nó dường như hoạt động khá tốt với tôi
Mat Roberts

33
ToArray là linq :)
Amy B

18
Đây là câu trả lời đúng nhất. Nó nhanh hơn cả câu hỏi và câu trả lời được chấp nhận và rõ ràng hơn nhiều so với Tổng hợp, đòi hỏi một lời giải thích dài cả đoạn mỗi khi nó được sử dụng.
PRman

@realPro Hoàn toàn sai. github.com/microsoft/referencesource/blob/master/mscorlib/ dòng line 161
Amy B

125

Tại sao nên sử dụng Linq?

string[] s = {"foo", "bar", "baz"};
Console.WriteLine(String.Join(", ", s));

Điều đó hoạt động hoàn hảo và chấp nhận bất kỳ IEnumerable<string>như tôi nhớ. Không cần Aggregatebất cứ điều gì ở đây là chậm hơn rất nhiều.


19
Học LINQ có thể rất tuyệt, và LINQ có thể là một phương tiện dễ thương để hoàn thành kết thúc, nhưng sử dụng LINQ để thực sự có được kết quả cuối cùng sẽ rất tệ, nói ít nhất, nếu không nói là hoàn toàn ngu ngốc
Jason Bunting

9
.NET 4.0 có quá tải <chuỗi> IEnumable <IE>, điều này sẽ giúp sử dụng dễ dàng hơn nhiều
Cine

3
Như Cine chỉ ra, .NET 4.0 có quá tải. Các phiên bản trước không. Bạn vẫn có thể String.Join(",", s.ToArray())trong các phiên bản cũ hơn.
Martijn

1
FYI: được hợp nhất từ stackoverflow.com/questions/122670/
triệt

@ Shog9 Sáp nhập làm cho câu trả lời ở đây trông giống như những nỗ lực trùng lặp và dấu thời gian không giúp ích gì cả .. Vẫn còn cách để đi.
nawfal

77

Bạn đã xem phương pháp mở rộng Tổng hợp chưa?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);

23
Điều đó có lẽ chậm hơn String.Join () và khó đọc mã hơn. Có trả lời câu hỏi cho "cách LINQ" không, mặc dù :-)
Chris Wenham

5
Vâng, tôi không muốn làm mờ câu trả lời với ý kiến ​​của mình. : P
Robert S.

2
Không nghi ngờ gì nữa, nó thực sự chậm hơn một chút. Ngay cả việc sử dụng Tổng hợp với StringBuilder thay vì ghép cũng chậm hơn String.Join.
Joel Mueller

4
Thực hiện một thử nghiệm với 10.000.000 lần lặp, tổng hợp mất 4,3 giây và chuỗi.join mất 2,3 giây. Vì vậy, tôi sẽ nói rằng diff diff là không quan trọng đối với 99% các trường hợp sử dụng phổ biến. Vì vậy, nếu bạn đã thực hiện nhiều linq để xử lý dữ liệu của mình, thường thì không cần phải phá vỡ cú pháp hay đó và sử dụng chuỗi.join imo. gist.github.com/joeriks/5791981
joeriks

1
FYI: được hợp nhất từ stackoverflow.com/questions/122670/
triệt

56

Ví dụ thực tế từ mã của tôi:

return selected.Select(query => query.Name).Aggregate((a, b) => a + ", " + b);

Truy vấn là một đối tượng có thuộc tính Tên là một chuỗi và tôi muốn tên của tất cả các truy vấn trong danh sách đã chọn, được phân tách bằng dấu phẩy.


2
Đưa ra các ý kiến ​​về hiệu suất, tôi nên thêm rằng ví dụ này là từ mã chạy một lần khi hộp thoại đóng lại và danh sách không có khả năng có nhiều hơn khoảng mười chuỗi trên đó!
Daniel Earwicker

Có manh mối nào để thực hiện nhiệm vụ tương tự trong Linq to Entities không?
Binoj Antony

1
Ví dụ tuyệt vời. Cảm ơn bạn đã đưa điều này vào một kịch bản thế giới thực. Tôi đã có tình huống chính xác tương tự, với một tài sản của một đối tượng cần che giấu.
Jessy Houle

1
Được khuyến khích giúp tôi tìm ra phần đầu tiên trong việc chọn thuộc tính chuỗi trong Danh sách của tôi <T>
Nikki9696

1
Hãy viết về hiệu suất của cách tiếp cận này với mảng lớn hơn.
Giulio Caccin

31

Dưới đây là cách tiếp cận Kết hợp / Linq kết hợp mà tôi đã giải quyết sau khi xem xét các câu trả lời khác và các vấn đề được giải quyết trong một câu hỏi tương tự (cụ thể là Tổng hợp và Kết hợp không thành công với 0 yếu tố).

string Result = String.Join(",", split.Select(s => s.Name));

hoặc (nếu skhông phải là một chuỗi)

string Result = String.Join(",", split.Select(s => s.ToString()));

  • Đơn giản
  • dễ đọc và dễ hiểu
  • làm việc cho các yếu tố chung
  • cho phép sử dụng các đối tượng hoặc thuộc tính đối tượng
  • xử lý trường hợp các phần tử có độ dài 0
  • có thể được sử dụng với bộ lọc Linq bổ sung
  • thực hiện tốt (ít nhất là theo kinh nghiệm của tôi)
  • không yêu cầu (thủ công) tạo một đối tượng bổ sung (ví dụ StringBuilder) để thực hiện

Và tất nhiên, Join sẽ chăm sóc dấu phẩy cuối cùng đáng tiếc mà đôi khi lén lút vào các phương pháp khác ( for, foreach), đó là lý do tại sao tôi tìm kiếm một giải pháp Linq ngay từ đầu.


1
sai ngoặc đơn.
ctrl-alt-delor 18/12/13

1
FYI: được hợp nhất từ stackoverflow.com/questions/122670/
triệt

3
Tôi thích câu trả lời này vì sử dụng .Select()như thế này cung cấp một nơi dễ dàng để sửa đổi từng thành phần trong thao tác này. Ví dụ: gói từng mục trong một số ký tự như vậystring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Sam Storie

29

Bạn có thể sử dụng StringBuildertrong Aggregate:

  List<string> strings = new List<string>() { "one", "two", "three" };

  StringBuilder sb = strings
    .Select(s => s)
    .Aggregate(new StringBuilder(), (ag, n) => ag.Append(n).Append(", "));

  if (sb.Length > 0) { sb.Remove(sb.Length - 2, 2); }

  Console.WriteLine(sb.ToString());

(Có Selecttrong đó chỉ để cho thấy bạn có thể làm nhiều công cụ LINQ hơn.)


2
+1 đẹp. Tuy nhiên, IMO tốt hơn là tránh thêm "," hơn là xóa nó sau đó. Một cái gì đó nhưnew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539

5
Bạn sẽ lưu các chu kỳ đồng hồ quý giá bằng cách không kiểm tra if (length > 0)linq và bằng cách lấy nó ra.
Binoj Antony

1
Tôi đồng ý với dss539. Phiên bản của tôi nằm dọc theo dòngnew[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
ProfNimrod

22

dữ liệu hiệu suất nhanh cho trường hợp StringBuilder vs Chọn & Tổng hợp trên 3000 phần tử:

Kiểm tra đơn vị - Thời lượng (giây)
LINQ_StringBuilder - 0,0036644
LINQ_Select.Aggregate - 1.8012535

    [TestMethod()]
    public void LINQ_StringBuilder()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000;i++ )
        {
            ints.Add(i);
        }
        StringBuilder idString = new StringBuilder();
        foreach (int id in ints)
        {
            idString.Append(id + ", ");
        }
    }
    [TestMethod()]
    public void LINQ_SELECT()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000; i++)
        {
            ints.Add(i);
        }
        string ids = ints.Select(query => query.ToString())
                         .Aggregate((a, b) => a + ", " + b);
    }

Hữu ích trong việc quyết định đi theo con đường không phải LINQ cho việc này
CrabCRUSHERclamCOLLECTOR

4
Sự khác biệt về thời gian có lẽ là StringBuilder so với String concatination bằng cách sử dụng +. Không có gì để làm với LINQ hoặc Uẩn. Đặt StringBuilder vào LINQ Aggregate (rất nhiều ví dụ về SO) và nó sẽ nhanh như vậy.
hộp điều khiển

16

Tôi luôn sử dụng phương thức mở rộng:

public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
    var ar = input.Select(i => i.ToString()).ToArray();
    return string.Join(seperator, ar);
}

5
string.Jointrong .net 4 có thể mất một IEnumerable<T>tùy ý T.
đệ quy

1
FYI: được hợp nhất từ stackoverflow.com/questions/122670/
triệt

12

Bằng ' cách LINQ cực hay ', bạn có thể nói về cách LINQ làm cho lập trình chức năng trở nên ngon miệng hơn rất nhiều khi sử dụng các phương thức mở rộng. Ý tôi là, đường cú pháp cho phép các chức năng được xâu chuỗi theo cách trực quan tuyến tính (cái này nối tiếp nhau) thay vì lồng nhau (cái này bên trong cái kia). Ví dụ:

int totalEven = Enumerable.Sum(Enumerable.Where(myInts, i => i % 2 == 0));

có thể được viết như thế này:

int totalEven = myInts.Where(i => i % 2 == 0).Sum();

Bạn có thể thấy ví dụ thứ hai dễ đọc hơn. Bạn cũng có thể thấy làm thế nào nhiều chức năng có thể được thêm vào với ít vấn đề thụt đầu dòng hơn hoặc các dấu đóng đóng Lispy xuất hiện ở cuối biểu thức.

Rất nhiều câu trả lời khác nói rằng đó String.Joinlà cách để đi bởi vì nó là cách nhanh nhất hoặc đơn giản nhất để đọc. Nhưng nếu bạn hiểu theo cách của tôi về ' cách LINQ siêu ngầu ' thì câu trả lời là sử dụng String.Joinnhưng hãy gói nó theo phương pháp mở rộng kiểu LINQ sẽ cho phép bạn xâu chuỗi các chức năng của mình theo cách trực quan. Vì vậy, nếu bạn muốn viết, sa.Concatenate(", ")bạn chỉ cần tạo ra một cái gì đó như thế này:

public static class EnumerableStringExtensions
{
   public static string Concatenate(this IEnumerable<string> strings, string separator)
   {
      return String.Join(separator, strings);
   }
}

Điều này sẽ cung cấp mã có hiệu suất như cuộc gọi trực tiếp (ít nhất là về độ phức tạp thuật toán) và trong một số trường hợp có thể làm cho mã dễ đọc hơn (tùy thuộc vào ngữ cảnh), đặc biệt nếu mã khác trong khối đang sử dụng kiểu hàm chuỗi .


1
Số lượng lỗi chính tả trong chủ đề này là điên rồ: seperator => separator, Concatinate => Concatenate
SilverSideDown

1
FYI: được hợp nhất từ stackoverflow.com/questions/122670/
triệt

5

Có nhiều câu trả lời khác nhau cho câu hỏi trước đó - được thừa nhận đang nhắm mục tiêu một mảng số nguyên làm nguồn, nhưng đã nhận được câu trả lời chung chung.


5

Ở đây, nó sử dụng LINQ thuần túy như một biểu thức:

static string StringJoin(string sep, IEnumerable<string> strings) {
  return strings
    .Skip(1)
    .Aggregate(
       new StringBuilder().Append(strings.FirstOrDefault() ?? ""), 
       (sb, x) => sb.Append(sep).Append(x));
}

Và nó khá nhanh chết tiệt!


3

Tôi sẽ gian lận một chút và đưa ra một câu trả lời mới cho điều này dường như tổng hợp những điều tốt nhất ở đây thay vì dán nó vào trong một bình luận.

Vì vậy, bạn có thể một dòng này:

List<string> strings = new List<string>() { "one", "two", "three" };

string concat = strings        
    .Aggregate(new StringBuilder("\a"), 
                    (current, next) => current.Append(", ").Append(next))
    .ToString()
    .Replace("\a, ",string.Empty); 

Chỉnh sửa: Trước tiên, bạn sẽ muốn kiểm tra vô số trống hoặc thêm một.Replace("\a",string.Empty); vào cuối biểu thức. Đoán tôi có thể đã cố gắng để có được một chút quá thông minh.

Câu trả lời từ @ a.friend có thể hiệu quả hơn một chút, tôi không chắc Thay thế nào dưới mui xe so với Xóa. Một cảnh báo duy nhất khác nếu một số lý do bạn muốn nối chuỗi kết thúc bằng \ a là bạn sẽ mất dấu phân cách của mình ... Tôi thấy điều đó là không thể. Nếu đó là trường hợp bạn có các nhân vật ưa thích khác để lựa chọn.


2

Bạn có thể kết hợp LINQ và string.join()khá hiệu quả. Ở đây tôi đang loại bỏ một mục từ một chuỗi. Có nhiều cách tốt hơn để làm điều này quá nhưng đây là:

filterset = String.Join(",",
                        filterset.Split(',')
                                 .Where(f => mycomplicatedMatch(f,paramToMatch))
                       );

1
FYI: được hợp nhất từ stackoverflow.com/questions/122670/
triệt

1

Rất nhiều sự lựa chọn ở đây. Bạn có thể sử dụng LINQ và StringBuilder để bạn có được hiệu suất như vậy:

StringBuilder builder = new StringBuilder();
List<string> MyList = new List<string>() {"one","two","three"};

MyList.ForEach(w => builder.Append(builder.Length > 0 ? ", " + w : w));
return builder.ToString();

Sẽ nhanh hơn nếu không kiểm tra builder.Length > 0ForEach và bằng cách xóa dấu phẩy đầu tiên sau ForEach
Binoj Antony

1

Tôi đã làm nhanh và bẩn sau đây khi phân tích tệp nhật ký IIS bằng linq, nó hoạt động khá tốt @ 1 triệu dòng (15 giây), mặc dù đã hết lỗi bộ nhớ khi thử 2 triệu dòng.

    static void Main(string[] args)
    {

        Debug.WriteLine(DateTime.Now.ToString() + " entering main");

        // USED THIS DOS COMMAND TO GET ALL THE DAILY FILES INTO A SINGLE FILE: copy *.log target.log 
        string[] lines = File.ReadAllLines(@"C:\Log File Analysis\12-8 E5.log");

        Debug.WriteLine(lines.Count().ToString());

        string[] a = lines.Where(x => !x.StartsWith("#Software:") &&
                                      !x.StartsWith("#Version:") &&
                                      !x.StartsWith("#Date:") &&
                                      !x.StartsWith("#Fields:") &&
                                      !x.Contains("_vti_") &&
                                      !x.Contains("/c$") &&
                                      !x.Contains("/favicon.ico") &&
                                      !x.Contains("/ - 80")
                                 ).ToArray();

        Debug.WriteLine(a.Count().ToString());

        string[] b = a
                    .Select(l => l.Split(' '))
                    .Select(words => string.Join(",", words))
                    .ToArray()
                    ;

        System.IO.File.WriteAllLines(@"C:\Log File Analysis\12-8 E5.csv", b);

        Debug.WriteLine(DateTime.Now.ToString() + " leaving main");

    }

Lý do thực sự tôi đã sử dụng linq là vì một Sự khác biệt () tôi cần trước đây:

string[] b = a
    .Select(l => l.Split(' '))
    .Where(l => l.Length > 11)
    .Select(words => string.Format("{0},{1}",
        words[6].ToUpper(), // virtual dir / service
        words[10]) // client ip
    ).Distinct().ToArray()
    ;

1
FYI: được hợp nhất từ stackoverflow.com/questions/122670/
triệt

0

Tôi đã viết về điều này một thời gian trước đây, những gì tôi đã thực hiện chính xác những gì bạn đang tìm kiếm:

http://ondevelopment.blogspot.com/2009/02/opes-concatenation-ADE-easy.html

Trong bài đăng trên blog mô tả cách triển khai các phương thức mở rộng hoạt động trên IEnumerable và được đặt tên là Concatenate, điều này sẽ cho phép bạn viết những thứ như:

var sequence = new string[] { "foo", "bar" };
string result = sequence.Concatenate();

Hoặc những thứ phức tạp hơn như:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");

1
FYI: được hợp nhất từ stackoverflow.com/questions/122670/
triệt

Bạn có thể ghép mã ở đây để câu trả lời dễ hiểu hơn không?
Giulio Caccin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.