Question
How can you create a comma-separated list of string values from an IList<string> or IEnumerable<string> in C#?
For example, suppose you have a collection of strings, but it is not stored as a string[]. You may have an IList<string> or an IEnumerable<string>, and converting it manually into an array just to call String.Join(...) can feel awkward.
What is the cleanest and most readable way to produce a single comma-separated string from such a collection?
Short Answer
By the end of this page, you will understand how to turn an IEnumerable<string> or IList<string> into a comma-separated string in C#. You will learn the idiomatic use of string.Join, how it works with collections, when conversion is unnecessary, and how to avoid common mistakes such as handling null values or adding separators manually.
Concept
In C#, a very common task is combining many string values into one string with a separator such as a comma, space, or pipe character.
The core concept here is joining a sequence of values into a single string.
If you have values like this:
var names = new List<string> { "Alice", "Bob", "Charlie" };
You often want this result:
"Alice,Bob,Charlie"
or this:
"Alice, Bob, Charlie"
In C#, the standard tool for this is string.Join.
A common beginner concern is that string.Join is only for arrays like string[]. That was a limitation in older usage patterns, but modern C# and .NET provide overloads that work directly with IEnumerable<string>.
That matters because many APIs return sequences such as:
List<string>IList<string>IEnumerable<string>- LINQ query results
- filtered or projected collections
Instead of building the string manually with loops, string.Join gives you a cleaner, safer, and more readable solution.
Why this matters in real programming:
- formatting user-visible text
- generating CSV-like output
- logging collections
- building query parameters
- combining tags, labels, or roles
Using the right built-in method reduces bugs like extra commas at the end and makes the code easier to read.
Mental Model
Think of string.Join like a machine that places glue between items in a line.
If your items are:
Alice Bob Charlie
and your glue is ", ", the machine produces:
Alice, Bob, Charlie
Important detail: the separator goes between items, not before the first one and not after the last one.
That is why string.Join is usually better than manually looping and appending commas yourself. It handles the separator placement for you.
Syntax and Examples
The basic syntax is:
string result = string.Join(separator, values);
Where:
separatoris a string such as", "valuesis a sequence of strings
Example with List<string>
using System;
using System.Collections.Generic;
var fruits = new List<string> { "Apple", "Banana", "Orange" };
string result = string.Join(", ", fruits);
Console.WriteLine(result);
Output:
Apple, Banana, Orange
Example with IEnumerable<string>
using System;
using System.Collections.Generic;
using System.Linq;
IEnumerable<string> shortWords = new[] { "cat", "elephant", "dog", "tiger" }
.Where(word => word.Length <= 5);
string result = string.Join(" | ", shortWords);
Console.WriteLine(result);
Output:
cat | dog | tiger
Example with IList<string>
using System;
using System.Collections.Generic;
IList<string> colors = new List<string> { "Red", "Green", "Blue" };
string result = string.Join(",", colors);
Console.WriteLine(result);
Output:
Red,Green,Blue
When values are not strings
If your collection contains another type, convert each item first:
using System;
using System.Linq;
var numbers = new[] { 10, 20, 30 };
string result = string.Join(", ", numbers.Select(n => n.ToString()));
Console.WriteLine(result);
Output:
10, 20, 30
In many real cases, if the values are already strings, you do not need to call ToArray() first.
Step by Step Execution
Consider this code:
using System;
using System.Collections.Generic;
IEnumerable<string> names = new List<string> { "Ana", "Ben", "Cara" };
string result = string.Join(", ", names);
Console.WriteLine(result);
Here is what happens step by step:
-
namesis created as a sequence containing three strings:"Ana""Ben""Cara"
-
string.Join(", ", names)is called.- The separator is
", " - The values come from the
IEnumerable<string>sequence
- The separator is
-
string.Joinreads the sequence items in order. -
It builds one final string by putting the separator between each item:
- Start with
"Ana" - Add
", "and"Ben" - Add
", "and"Cara"
- Start with
-
The final result becomes:
Ana, Ben, Cara
Console.WriteLine(result)prints that final string.
Trace example with filtering
using System;
using System.Linq;
var words = new[] { "sun", "moon", "star", "sky" };
var filtered = words.Where(w => w.Length <= 4);
string result = string.Join("; ", filtered);
Console.WriteLine(result);
Step-by-step:
wordscontainssun,moon,star,skyWhere(w => w.Length <= 4)keeps all of them in this casestring.Join("; ", filtered)combines them with;- Output is:
sun; moon; star; sky
Real World Use Cases
This pattern appears in many practical situations.
Displaying tags or labels
var tags = new List<string> { "csharp", "linq", "strings" };
string display = string.Join(", ", tags);
Useful for UI display such as:
csharp, linq, strings
Logging selected items
var selectedIds = new List<string> { "A12", "B34", "C56" };
logger.LogInformation("Selected items: {Items}", string.Join(", ", selectedIds));
Building query parameters or route pieces
var ids = new List<string> { "10", "20", "30" };
string csv = string.Join(",", ids);
That may be sent to an API as:
ids=10,20,30
Exporting data
var columns = new[] { "Name", "Email", "Country" };
string header = string.Join(",", columns);
Result:
Name,Email,Country
Formatting validation messages
var missingFields = new List<string> { "Email", "Password" };
string message = "Missing required fields: " + string.Join(", ", missingFields);
Real Codebase Usage
In real codebases, developers often use string.Join together with LINQ and validation.
Pattern: joining projected values
Sometimes the original collection contains objects, not strings.
var users = new[]
{
new { Name = "Ava" },
new { Name = "Noah" },
new { Name = "Liam" }
};
string names = string.Join(", ", users.Select(u => u.Name));
This is very common when formatting lists from domain objects.
Pattern: guard clause before joining
if (items == null)
{
return string.Empty;
}
return string.Join(", ", items);
This protects your code from a null collection reference.
Pattern: filtering empty values
var cleaned = values.Where(v => !string.IsNullOrWhiteSpace(v));
string result = string.Join(", ", cleaned);
This avoids output like:
Alice, , Bob,
Pattern: configuration or serialization helpers
Developers often join string collections when producing:
- configuration values
- cache keys
- display summaries
- SQL parameter logs
- audit messages
Pattern: early return for empty data
if (!values.Any())
{
return "No items";
}
return string.Join(", ", values);
This makes output more meaningful when the sequence is empty.
Common Mistakes
1. Manually adding commas in a loop
Broken or awkward approach:
var result = "";
foreach (var item in items)
{
result += item + ",";
}
Problems:
- leaves a trailing comma
- less readable
- repeated string concatenation can be inefficient
Better:
string result = string.Join(",", items);
2. Assuming ToArray() is always required
Unnecessary code:
string result = string.Join(", ", items.ToArray());
If items is already an IEnumerable<string> and your framework supports the overload, this is usually unnecessary.
Better:
string result = string.Join(", ", items);
3. Forgetting to handle null collections
Broken code:
IEnumerable<string> items = null;
string result = string.Join(", ", items);
Depending on context, this can fail because items itself is null.
Safer:
string result = string.Join(", ", items ?? Enumerable.Empty<string>());
4. Confusing null items with a null collection
var items = new List<string> { "A", null, "C" };
string result = string.Join(", ", items);
This is valid. A null element is treated like an empty string in the joined output.
Possible result:
A, , C
If you do not want that, filter values first:
string result = string.Join(", ", items.Where(x => !string.IsNullOrEmpty(x)));
5. Using the wrong separator
string result = string.Join(",", items);
This gives:
A,B,C
If you want spaces after commas, use:
string result = string.Join(", ", items);
Comparisons
| Approach | Example | Good for | Downsides |
|---|---|---|---|
string.Join | string.Join(", ", items) | Cleanly combining values with a separator | None for normal string-joining use |
| Manual loop + concatenation | result += item + ","; | Very simple demos | Trailing separators, less efficient, harder to read |
StringBuilder loop | Append items manually | Complex custom formatting | More code for a simple join |
Aggregate | items.Aggregate((a, b) => a + ", " + b) | Functional style experiments | Harder to read, can fail on empty sequences |
string.Join vs Aggregate
string result = string.Join(", ", items);
vs.
string result = items.Aggregate((a, b) => a + ", " + b);
string.Join is usually better because:
- it is clearer
- it handles empty sequences more safely
- it directly expresses your intent
IList<string> vs IEnumerable<string>
| Type | What it means |
|---|---|
IList<string> | A collection you can index, like items[0] |
IEnumerable<string> | Any sequence you can iterate through |
For string.Join, both are commonly fine because an IList<string> is also an IEnumerable<string> in practice for standard collection types.
Cheat Sheet
// Best common solution
string result = string.Join(", ", values);
Common separators
string.Join(",", values); // A,B,C
string.Join(", ", values); // A, B, C
string.Join(" | ", values); // A | B | C
string.Join(Environment.NewLine, values); // one per line
With LINQ projection
string result = string.Join(", ", users.Select(u => u.Name));
Safe null handling
string result = string.Join(", ", values ?? Enumerable.Empty<string>());
Filter blanks first
string result = string.Join(", ", values.Where(v => !string.IsNullOrWhiteSpace(v)));
Key points
- Use
string.Joinfor separator-based string building. - Prefer it over manual loops for readability.
- You usually do not need
ToArray()forIEnumerable<string>. - A
nullelement becomes an empty part in the output. - A
nullcollection should be handled before joining.
FAQ
Can string.Join work with IEnumerable<string> in C#?
Yes. In modern .NET, string.Join has overloads that work directly with IEnumerable<string>.
Do I need to convert a List<string> to an array before calling string.Join?
No, not usually. A List<string> can be passed directly to the appropriate overload.
What happens if one item in the collection is null?
That item is treated like an empty string in the output.
What happens if the whole collection is null?
You should guard against that case, for example with values ?? Enumerable.Empty<string>().
Is string.Join better than using a loop?
Yes for this task. It is shorter, clearer, and avoids separator-placement bugs.
Can I join values from objects instead of strings?
Yes. Use Select to project the property you want, then pass that sequence to string.Join.
Should I use Aggregate instead of string.Join?
Usually no. string.Join is the clearer and more idiomatic choice for combining strings with separators.
Mini Project
Description
Build a small C# utility that formats a list of user-entered tags into a clean comma-separated string. This demonstrates how to join an IEnumerable<string>, remove blank entries, and produce user-friendly output that could be used in a console app, API, or admin tool.
Goal
Create a method that takes a sequence of strings, removes empty entries, and returns a clean comma-separated result.
Requirements
- Create a method that accepts an
IEnumerable<string>. - Ignore
null, empty, or whitespace-only values. - Return the remaining values as a comma-and-space separated string.
- Return an empty string if there are no valid values.
- Demonstrate the method with sample input in a console application.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Access Modifiers and static: public, private, protected, and Defaults
Learn how C# public, private, protected, and default access control visibility, and how static differs from instance members.