Quel est le moyen le plus efficace d’écrire la vieille école:
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();
... dans LINQ?
Cette réponse montre l'utilisation de LINQ (Aggregate
) comme demandé dans la question et n'est pas destinée à un usage quotidien. Parce que cela n'utilise pas de StringBuilder
, ses performances seront horribles pour de très longues séquences. Pour le code normal, utilisez String.Join
comme indiqué dans l'autre réponse
Utilisez des requêtes globales comme ceci:
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);
Cela génère:
, un deux trois
Un agrégat est une fonction qui prend une collection de valeurs et renvoie une valeur scalaire. Les exemples de T-SQL incluent min, max et sum. VB et C # prennent tous deux en charge les agrégats. VB et C # prennent en charge les agrégats en tant que méthodes d'extension. En utilisant la notation par points, on appelle simplement une méthode sur un objet IEnumerable .
N'oubliez pas que les requêtes globales sont exécutées immédiatement.
Plus d'informations - MSDN: Aggregate Queries
Si vous voulez vraiment utiliser Aggregate
utilisez une variante utilisant StringBuilder
proposé dans le commentaire de CodeMonkeyKing , ce qui correspondrait au même code que le String.Join
normal, avec de bonnes performances pour les gros nombre d'objets:
var res = words.Aggregate(
new StringBuilder(),
(current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
.ToString();
return string.Join(", ", strings.ToArray());
Dans .Net 4, il existe un nouveau surcharge pour string.Join
qui accepte IEnumerable<string>
. Le code ressemblerait alors à ceci:
return string.Join(", ", strings);
Pourquoi utiliser Linq?
string[] s = {"foo", "bar", "baz"};
Console.WriteLine(String.Join(", ", s));
Cela fonctionne parfaitement et accepte n'importe quel IEnumerable<string>
autant que je m'en souvienne. Pas besoin de Aggregate
quoi que ce soit ici qui est beaucoup plus lent.
Avez-vous examiné la méthode d'extension Aggregate?
var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Exemple réel de mon code:
return selected.Select(query => query.Name).Aggregate((a, b) => a + ", " + b);
Une requête est un objet qui a une propriété Name qui est une chaîne et je veux les noms de toutes les requêtes de la liste sélectionnée, séparés par des virgules.
Voici l’approche combinée Join/Linq sur laquelle j’ai opté après avoir examiné les autres réponses et les problèmes abordés dans une question similaire (à savoir que Aggregate et Concatenate échouent avec 0 élément).
string Result = String.Join(",", split.Select(s => s.Name));
ou (si s
n'est pas une chaîne)
string Result = String.Join(",", split.Select(s => s.ToString()));
StringBuilder
) à implémenterEt bien sûr, Join s’occupe de la dernière virgule embêtante qui se glisse parfois dans d’autres approches (for
, foreach
), c’est pourquoi j’étais à la recherche d’une solution Linq.
Vous pouvez utiliser StringBuilder
dans 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());
(La Select
est là juste pour montrer que vous pouvez faire plus de choses avec LINQ.)
données de performances rapides pour la casse StringBuilder vs Select & Aggregate de plus de 3000 éléments:
Test unitaire - Durée (secondes)
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);
}
J'utilise toujours la méthode d'extension:
public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
var ar = input.Select(i => i.ToString()).ToArray();
return string.Join(seperator, ar);
}
Par 'façon LINQ super-cool', vous parlez peut-être de la manière dont LINQ rend la programmation fonctionnelle beaucoup plus acceptable avec l'utilisation de méthodes d'extension. Je veux dire, le sucre syntaxique qui permet d’enchaîner les fonctions de manière linéaire (l’une après l’autre) au lieu de l’imbrication (l’une dans l’autre). Par exemple:
int totalEven = Enumerable.Sum(Enumerable.Where(myInts, i => i % 2 == 0));
peut être écrit comme ceci:
int totalEven = myInts.Where(i => i % 2 == 0).Sum();
Vous pouvez voir comment le deuxième exemple est plus facile à lire. Vous pouvez également voir comment plus de fonctions peuvent être ajoutées avec moins de problèmes d'indentation ou de parenthèses Lispy apparaissant à la fin de l'expression.
Beaucoup d'autres réponses indiquent que le String.Join
est le chemin à suivre car c'est le plus rapide ou le plus simple à lire. Mais si vous prenez mon interprétation de 'façon LINQ super-cool', alors la réponse consiste à utiliser String.Join
mais à l'envelopper dans une méthode d'extension de style LINQ qui vous permettra d'enchaîner vos fonctions. de manière visuellement agréable. Donc, si vous voulez écrire sa.Concatenate(", ")
, il vous suffit de créer quelque chose comme ceci:
public static class EnumerableStringExtensions
{
public static string Concatenate(this IEnumerable<string> strings, string separator)
{
return String.Join(separator, strings);
}
}
Cela fournira un code aussi performant que l'appel direct (du moins en termes de complexité de l'algorithme) et, dans certains cas, rendra le code plus lisible (en fonction du contexte), en particulier si un autre code du bloc utilise le style de fonction chaînée. .
Ici, il utilise LINQ pur comme une expression unique:
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));
}
Et c'est sacrément rapide!
Il existe différentes réponses alternatives à ce question précédente - qui visait certes un tableau entier comme source, mais qui a reçu des réponses généralisées.
Je vais tricher un peu et proposer une nouvelle réponse à cette question qui semble résumer le meilleur de tout ce qui se passe ici au lieu de la coller dans un commentaire.
Donc, vous pouvez en une ligne ceci:
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);
Éditer: Vous voudrez d’abord vérifier si un énumérable est vide ou ajouter un .Replace("\a",string.Empty);
à la fin de l’expression. J'imagine que j'ai peut-être essayé d'être un peu trop intelligent.
La réponse de @ a.friend pourrait être légèrement plus performante, je ne sais pas ce que Remplacer fait sous le capot par rapport à Supprimer. Le seul autre inconvénient, si vous vouliez concaténer des chaînes qui se terminaient par un\a, vous perdriez vos séparateurs ... Je trouve cela improbable. Si tel est le cas, vous avez le choix entre autres caractères de fantaisie .
Vous pouvez combiner LINQ et string.join()
de manière très efficace. Ici, je supprime un élément d'une chaîne. Il y a de meilleures façons de le faire aussi mais le voici:
filterset = String.Join(",",
filterset.Split(',')
.Where(f => mycomplicatedMatch(f,paramToMatch))
);
Beaucoup de choix ici. Vous pouvez utiliser LINQ et un StringBuilder pour que vous obteniez aussi les performances suivantes:
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();
Lors de l'analyse syntaxique d'un fichier journal IIS à l'aide de linq, cela fonctionnait plutôt bien avec 1 million de lignes (15 secondes).
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");
}
La vraie raison pour laquelle j'ai utilisé linq était pour Distinct () dont j'avais besoin auparavant:
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()
;
J'ai blogué à ce sujet il y a quelque temps, ce que j'ai fait semble être exactement ce que vous recherchez:
http://ondevelopment.blogspot.com/2009/02/string-concatenation-made-easy.html
Dans l'article de blog, décrivez comment implémenter des méthodes d'extension fonctionnant sur IEnumerable et nommées Concatenate. Cela vous permettra d'écrire des choses comme:
var sequence = new string[] { "foo", "bar" };
string result = sequence.Concatenate();
Ou des choses plus élaborées comme:
var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");