Comment joindre int [] à une chaîne séparée par des caractères dans .NET?

101

J'ai un tableau d'entiers:

int[] number = new int[] { 2,3,6,7 };

Quelle est la manière la plus simple de les convertir en une seule chaîne où les nombres sont séparés par un caractère (comme:) "2,3,6,7"?

Je suis en C # et .NET 3.5.

Riri
la source
3
TELLEMENT rock! J'ai eu ces 3 excellentes réponses en 10 minutes un dimanche!
Riri
4
À partir de .NET 4.0là, il existe des méthodes qui prennent un tableau d'objets et un IEnumerable, vous pouvez donc le faire string.join(",", number). Je sais que la question spécifie .NET 3.5, donc je n'en ai pas fait une réponse, mais cela apparaît dans les recherches qui ne spécifient pas de version et savoir que c'est possible dans 4.0 pourrait aider quelqu'un.
Jason Goemaat

Réponses:

162
var ints = new int[] {1, 2, 3, 4, 5};
var result = string.Join(",", ints.Select(x => x.ToString()).ToArray());
Console.WriteLine(result); // prints "1,2,3,4,5"

EDIT : à partir de (au moins) .NET 4.5,

var result = string.Join(",", ints.Select(x => x.ToString()).ToArray());

est équivalent à:

var result = string.Join(",", ints);

MODIFIER :

Je vois plusieurs solutions annoncer l'utilisation de StringBuilder. Quelqu'un se plaint que la méthode Join doit prendre un argument IEnumerable.

Je vais vous décevoir :) String.Join nécessite un tableau pour une seule raison: les performances. La méthode de jointure doit connaître la taille des données pour préallouer efficacement la quantité de mémoire nécessaire.

Voici une partie de l'implémentation interne de la méthode String.Join:

// length computed from length of items in input array and length of separator
string str = FastAllocateString(length);
fixed (char* chRef = &str.m_firstChar) // note than we use direct memory access here
{
    UnSafeCharBuffer buffer = new UnSafeCharBuffer(chRef, length);
    buffer.AppendString(value[startIndex]);
    for (int j = startIndex + 1; j <= num2; j++)
    {
        buffer.AppendString(separator);
        buffer.AppendString(value[j]);
    }
}

Je suis trop paresseux pour comparer les performances des méthodes suggérées. Mais quelque chose me dit que Join gagnera :)

aku
la source
C'est probablement le meilleur pari en utilisant les méthodes d'extension de base .NET, mais j'aurais vraiment souhaité que string.Join () accepte un IEnumerable <string> pour éviter la conversion ToArray ().
spoulson
Rien n'empêche quelqu'un de surcharger la chaîne, mais aussi de prendre un IEnumerable. ;)
Robert P
1
C'est probablement aussi la solution la plus simple, et pas seulement la plus rapide.
Dave Van den Eynde
9
.NET 4 fournit une surcharge String.Join qui accepte IEnumerable comme paramètre. msdn.microsoft.com/en-us/library/dd783876.aspx
Ryan Kohn
using System.Linq;est requis.
Gayan Weerakutti
32

Bien que l'OP ait spécifié .NET 3.5, les personnes souhaitant le faire dans .NET 2.0 avec C # 2 peuvent le faire:

string.Join(",", Array.ConvertAll<int, String>(ints, Convert.ToString));

Je trouve qu'il existe un certain nombre d'autres cas où l'utilisation des fonctions Convert.xxx est une alternative plus intéressante à un lambda, bien qu'en C # 3, le lambda puisse aider l'inférence de type.

Une version C # 3 assez compacte qui fonctionne avec .NET 2.0 est la suivante:

string.Join(",", Array.ConvertAll(ints, item => item.ToString()))
Will Dean
la source
11

Un mélange des deux approches consisterait à écrire une méthode d'extension sur IEnumerable <T> qui utilise un StringBuilder. Voici un exemple, avec des surcharges différentes selon que vous souhaitez spécifier la transformation ou simplement compter sur ToString brut. J'ai nommé la méthode "JoinStrings" au lieu de "Join" pour éviter toute confusion avec l'autre type de Join. Peut-être que quelqu'un peut trouver un meilleur nom :)

using System;
using System.Collections.Generic;
using System.Text;

public static class Extensions
{
    public static string JoinStrings<T>(this IEnumerable<T> source, 
                                        Func<T, string> projection, string separator)
    {
        StringBuilder builder = new StringBuilder();
        bool first = true;
        foreach (T element in source)
        {
            if (first)
            {
                first = false;
            }
            else
            {
                builder.Append(separator);
            }
            builder.Append(projection(element));
        }
        return builder.ToString();
    }

    public static string JoinStrings<T>(this IEnumerable<T> source, string separator)
    {
        return JoinStrings(source, t => t.ToString(), separator);
    }
}

class Test
{

    public static void Main()
    {
        int[] x = {1, 2, 3, 4, 5, 10, 11};

        Console.WriteLine(x.JoinStrings(";"));
        Console.WriteLine(x.JoinStrings(i => i.ToString("X"), ","));
    }
}
Jon Skeet
la source
Belle solution! Cependant, vous n'avez pas besoin du paramètre de projection, vous pouvez simplement écrire x.Select (i => i.ToString ("X")). JoinStrings (";") qui est plus idiomatique.
JacquesB
Oui, j'y ai pensé après. Parfois, c'est bien de pouvoir tout spécifier en une seule fois, mais il est nettement plus élégant de diviser les opérations :)
Jon Skeet
8
String.Join(";", number.Select(item => item.ToString()).ToArray());

Nous devons convertir chacun des éléments en un Stringavant de pouvoir les joindre, il est donc logique d'utiliser Selectet une expression lambda. Ceci est équivalent à mapdans certaines autres langues. Ensuite, nous devons reconvertir la collection résultante de chaînes en un tableau, car String.Joinn'accepte qu'un tableau de chaînes.

Le ToArray()est un peu moche je trouve. String.Joindevrait vraiment accepter IEnumerable<String>, il n'y a aucune raison de le limiter aux seuls tableaux. C'est probablement juste parce que Joinc'est avant les génériques, quand les tableaux étaient le seul type de collection typée disponible.

JacquesB
la source
5

Si votre tableau d'entiers peut être volumineux, vous obtiendrez de meilleures performances en utilisant un StringBuilder. Par exemple:

StringBuilder builder = new StringBuilder();
char separator = ',';
foreach(int value in integerArray)
{
    if (builder.Length > 0) builder.Append(separator);
    builder.Append(value);
}
string result = builder.ToString();

Edit: Quand j'ai posté ceci, j'avais l'impression erronée que "StringBuilder.Append (valeur int)" a réussi en interne à ajouter la représentation sous forme de chaîne de la valeur entière sans créer d'objet string. C'est faux: l'inspection de la méthode avec Reflector montre qu'elle ajoute simplement value.ToString ().

Par conséquent, la seule différence de performances potentielle est que cette technique évite la création d'un tableau et libère les chaînes pour le garbage collection un peu plus tôt. En pratique, cela ne fera aucune différence mesurable, j'ai donc voté pour cette meilleure solution .

Joe
la source
L'avez-vous mesuré pour être plus rapide? String.Join utilise également un StringBuilder.
JacquesB
Oui, mais vous devez d'abord convertir le tout en un tableau, ce qui est loin d'être idéal. En particulier, cela signifie que vous devez avoir toutes les chaînes converties en mémoire en même temps, avant de commencer à créer la chaîne résultante.
Jon Skeet
OTOH String.Join précalcule la taille du tampon StringBuilder afin d'éviter le redimensionnement. Cela pourrait donc être plus rapide même si cela nécessite plus de mémoire.
JacquesB
5

La question est de savoir "le moyen le plus simple de les convertir en une seule chaîne où les nombres sont séparés par un caractère".

Le moyen le plus simple est:

int[] numbers = new int[] { 2,3,6,7 };
string number_string = string.Join(",", numbers);
// do whatever you want with your exciting new number string

EDIT: Cela ne fonctionne que dans .NET 4.0+, j'ai manqué l'exigence .NET 3.5 la première fois que j'ai lu la question.

WebMasterP
la source
Ce n'est pas valide en tant que chaîne.La méthodeJoin prend uniquement un tableau de chaînes. Jetez un œil ici msdn.microsoft.com/en-us/library/tk0xe5h0.aspx
ppolyzos
1
C'est une méthode surchargée: msdn.microsoft.com/en-us/library/dd988350 Je viens de copier le code que j'ai écrit dans une nouvelle application console, j'ai ajouté une Console.WriteLine et voici le résultat: 2,3,6,7
WebMasterP
1
Je pense que cela n'est disponible qu'en .net 4
Govind Malviya
Nécessite un tableau d'objets (ou un tableau de chaînes), pas un tableau int. Donnera une erreur "l'appel est ambigu".
LarryBud
2

Je suis d'accord avec l'expression lambda pour la lisibilité et la maintenabilité, mais ce ne sera pas toujours la meilleure option. L'inconvénient d'utiliser à la fois les approches IEnumerable / ToArray et StringBuilder est qu'ils doivent développer dynamiquement une liste, soit d'éléments, soit de caractères, car ils ne savent pas combien d'espace sera nécessaire pour la chaîne finale.

Si le cas rare où la vitesse est plus importante que la concision, ce qui suit est plus efficace.

int[] number = new int[] { 1, 2, 3, 4, 5 };
string[] strings = new string[number.Length];
for (int i = 0; i < number.Length; i++)
  strings[i] = number[i].ToString();
string result = string.Join(",", strings);
DocMax
la source
2
ints.Aggregate("", ( str, n ) => str +","+ n ).Substring(1);

J'ai aussi pensé qu'il y avait un moyen plus simple. Vous ne connaissez pas la performance, quelqu'un a une idée (théorique)?

néant
la source
Cette solution vous donnerait ", 1,2,3,4,5".
Sarin
Merci, j'ai ajouté Substring(1)pour résoudre ce problème (c'était de mémoire).
annulé
2

Dans .NET 4.0, la jointure de chaîne a une surcharge pour params object[], donc c'est aussi simple que:

int[] ids = new int[] { 1, 2, 3 };
string.Join(",", ids);

exemple

int[] ids = new int[] { 1, 2, 3 };
System.Data.Common.DbCommand cmd = new System.Data.SqlClient.SqlCommand("SELECT * FROM some_table WHERE id_column IN (@bla)");
cmd.CommandText = cmd.CommandText.Replace("@bla",  string.Join(",", ids));

Dans .NET 2.0, c'est un tout petit peu plus difficile, car il n'y a pas de telle surcharge. Vous avez donc besoin de votre propre méthode générique:

public static string JoinArray<T>(string separator, T[] inputTypeArray)
{
    string strRetValue = null;
    System.Collections.Generic.List<string> ls = new System.Collections.Generic.List<string>();

    for (int i = 0; i < inputTypeArray.Length; ++i)
    {
        string str = System.Convert.ToString(inputTypeArray[i], System.Globalization.CultureInfo.InvariantCulture);

        if (!string.IsNullOrEmpty(str))
        { 
            // SQL-Escape
            // if (typeof(T) == typeof(string))
            //    str = str.Replace("'", "''");

            ls.Add(str);
        } // End if (!string.IsNullOrEmpty(str))

    } // Next i 

    strRetValue= string.Join(separator, ls.ToArray());
    ls.Clear();
    ls = null;

    return strRetValue;
}

Dans .NET 3.5, vous pouvez utiliser des méthodes d'extension:

public static class ArrayEx
{

    public static string JoinArray<T>(this T[] inputTypeArray, string separator)
    {
        string strRetValue = null;
        System.Collections.Generic.List<string> ls = new System.Collections.Generic.List<string>();

        for (int i = 0; i < inputTypeArray.Length; ++i)
        {
            string str = System.Convert.ToString(inputTypeArray[i], System.Globalization.CultureInfo.InvariantCulture);

            if (!string.IsNullOrEmpty(str))
            { 
                // SQL-Escape
                // if (typeof(T) == typeof(string))
                //    str = str.Replace("'", "''");

                ls.Add(str);
            } // End if (!string.IsNullOrEmpty(str))

        } // Next i 

        strRetValue= string.Join(separator, ls.ToArray());
        ls.Clear();
        ls = null;

        return strRetValue;
    }

}

Vous pouvez donc utiliser la méthode d'extension JoinArray.

int[] ids = new int[] { 1, 2, 3 };
string strIdList = ids.JoinArray(",");

Vous pouvez également utiliser cette méthode d'extension dans .NET 2.0, si vous ajoutez ExtensionAttribute à votre code:

// you need this once (only), and it must be in this namespace
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
    public sealed class ExtensionAttribute : Attribute {}
}
Stefan Steiger
la source