web-dev-qa-db-fra.com

Utiliser LINQ pour concaténer des chaînes

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?

324
tags2k

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();
515
Jorge Ferreira
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);
329
Amy B

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.

124
Armin Ronacher

Avez-vous examiné la méthode d'extension Aggregate?

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

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.

56
Daniel Earwicker

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()));

  • Facile
  • facile à lire et à comprendre
  • fonctionne pour les éléments génériques
  • permet d'utiliser des objets ou des propriétés d'objet
  • gère le cas d'éléments de longueur nulle
  • pourrait être utilisé avec un filtrage Linq supplémentaire
  • se comporte bien (du moins d'après mon expérience)
  • ne nécessite pas la création (manuelle) d'un objet supplémentaire (par exemple StringBuilder) à implémenter

Et 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.

28
brichins

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.)

28
jonathan.s

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);
    }
22
user337754

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);
}
16
Kieran Benton

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. .

12
tpower

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!

5
cdiggins

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.

5
Jon Skeet

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 .

3
Chris Marisic

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))
                       );
2
Andiih

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();
1
Kelly

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()
    ;
1
Andy S.

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(", ");
0
Patrik Hägne