Jak dołączyć int [] do ciągu oddzielonego znakami w .NET?

101

Mam tablicę liczb całkowitych:

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

Jaki jest najłatwiejszy sposób przekształcenia ich w pojedynczy ciąg, w którym liczby są oddzielone znakiem (na przykład "2,3,6,7":)?

Jestem w C # i .NET 3.5.

Riri
źródło
3
TAK Skały! W niedzielę otrzymałem 3 doskonałe odpowiedzi w ciągu 10 minut!
Riri
4
Ponieważ .NET 4.0istnieją metody, które pobierają tablicę obiektów i IEnumerable, więc możesz to zrobić string.join(",", number). Wiem, że pytanie określa .NET 3.5, więc nie udzieliłem odpowiedzi, ale pojawia się w wyszukiwaniach, które nie określają wersji, a wiedza, że ​​jest to możliwe w 4.0, może komuś pomóc.
Jason Goemaat

Odpowiedzi:

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"

EDYCJA : od (przynajmniej) .NET 4.5,

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

jest równa:

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

EDYCJA :

Widzę, że kilka rozwiązań reklamuje użycie StringBuilder. Ktoś narzeka, że ​​metoda Join powinna przyjmować argument IEnumerable.

Zawiodę Cię :) String.Join wymaga tablicy z jednego powodu - wydajności. Metoda Join musi znać rozmiar danych, aby skutecznie przydzielić niezbędną ilość pamięci.

Oto fragment wewnętrznej implementacji metody 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]);
    }
}

Jestem zbyt leniwy, aby porównać wydajność sugerowanych metod. Ale coś mi mówi, że Join wygra :)

aku
źródło
Jest to prawdopodobnie najlepszy zakład przy użyciu podstawowych metod rozszerzających .NET, ale naprawdę chciałem, aby string.Join () zaakceptował IEnumerable <string>, aby uniknąć konwersji ToArray ().
spoulson
Nic nie stoi na przeszkodzie, aby ktoś przeciążał string.Join, aby również wziąć IEnumerable. ;)
Robert P
1
Jest to prawdopodobnie również najłatwiejsze rozwiązanie, a nie tylko najszybsze.
Dave Van den Eynde
9
NET 4 udostępnia Przeciążenie String.Join, które akceptuje IEnumerable jako parametr. msdn.microsoft.com/en-us/library/dd783876.aspx
Ryan Kohn
using System.Linq;jest wymagane.
Gayan Weerakutti,
32

Chociaż w OP określono .NET 3.5, osoby, które chcą to zrobić w .NET 2.0 z C # 2, mogą to zrobić:

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

Uważam, że istnieje wiele innych przypadków, w których użycie funkcji Convert.xxx jest lepszą alternatywą dla lambdy, chociaż w języku C # 3 lambda może pomóc w wnioskowaniu o typie.

Dość kompaktowa wersja C # 3, która współpracuje z .NET 2.0 to:

string.Join(",", Array.ConvertAll(ints, item => item.ToString()))
Will Dean
źródło
11

Jedną z mieszanin tych dwóch podejść byłoby napisanie metody rozszerzenia w IEnumerable <T>, która używała StringBuilder. Oto przykład z różnymi przeciążeniami w zależności od tego, czy chcesz określić transformację, czy po prostu polegać na zwykłym ToString. Nazwałem metodę „JoinStrings” zamiast „Join”, aby uniknąć pomyłki z innym typem Join. Może ktoś wymyśli lepszą nazwę :)

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
źródło
Niezłe rozwiązanie! Nie potrzebujesz jednak parametru projekcji, możesz po prostu napisać x.Select (i => i.ToString ("X")). JoinStrings (";"), co jest bardziej idiomatyczne.
JacquesB
Tak, myślałem o tym później. Czasami miło jest móc określić to wszystko za jednym zamachem, ale zdecydowanie bardziej elegancko jest podzielić operacje :)
Jon Skeet
8
String.Join(";", number.Select(item => item.ToString()).ToArray());

Musimy przekonwertować każdy z elementów na a, Stringzanim będziemy mogli je połączyć, więc sensowne jest użycie Selecti wyrażenie lambda. Jest to równoważne mapw niektórych innych językach. Następnie musimy przekonwertować wynikowy zbiór ciągów z powrotem na tablicę, ponieważString.Join akceptuje tylko tablicę ciągów.

ToArray()Jest nieco brzydki myślę. String.Joinpowinien naprawdę zaakceptować IEnumerable<String>, nie ma powodu, aby ograniczać go tylko do tablic. Dzieje się tak prawdopodobnie dlatego, że Joinpochodzi z czasów przed typami generycznymi, kiedy tablice były jedynym dostępnym rodzajem kolekcji.

JacquesB
źródło
5

Jeśli twoja tablica liczb całkowitych może być duża, uzyskasz lepszą wydajność, używając StringBuilder. Na przykład:

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

Edycja: Kiedy to opublikowałem, miałem błędne wrażenie, że „StringBuilder.Append (int value)” wewnętrznie zdołał dołączyć ciąg znaków reprezentujący wartość całkowitą bez tworzenia obiektu ciągu. To jest złe: sprawdzanie metody za pomocą Reflectora pokazuje, że po prostu dołącza value.ToString ().

Dlatego jedyną potencjalną różnicą w wydajności jest to, że ta technika pozwala uniknąć tworzenia jednej tablicy i nieco wcześniej zwalnia ciągi do wyrzucania elementów bezużytecznych. W praktyce nie da to żadnej wymiernej różnicy, więc głosowałem za tym lepszym rozwiązaniem .

Joe
źródło
Czy zmierzyłeś, że jest szybszy? String.Join używa również StringBuilder.
JacquesB
Tak, ale najpierw musisz przekonwertować całość na tablicę, która jest daleka od ideału. W szczególności oznacza to, że musisz mieć wszystkie przekonwertowane ciągi w pamięci w tym samym czasie, zanim zaczniesz budować wynikowy ciąg.
Jon Skeet
OTOH String.Join wstępnie oblicza rozmiar buforu StringBuilder, aby uniknąć zmiany rozmiaru. Więc może być szybszy, nawet jeśli wymaga więcej pamięci.
JacquesB
5

Pytanie dotyczy „najłatwiejszego sposobu przekształcenia ich w pojedynczy ciąg, w którym liczby są oddzielone znakiem”.

Najłatwiej jest:

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

EDYCJA: To działa tylko w .NET 4.0+, brakowało mi wymagania .NET 3.5, gdy po raz pierwszy przeczytałem pytanie.

WebMasterP
źródło
Nie jest to poprawne, ponieważ metoda string.Join przyjmuje tylko tablicę ciągów. Zajrzyj tutaj msdn.microsoft.com/en-us/library/tk0xe5h0.aspx
ppolyzos
1
Jest to metoda przeciążona: msdn.microsoft.com/en-us/library/dd988350 Właśnie skopiowałem kod, który napisałem do nowej aplikacji konsolowej, dodałem Console.WriteLine i to jest wyjście: 2,3,6,7
WebMasterP,
1
Myślę, że jest to dostępne tylko w .net 4
Govind Malviya
Wymaga tablicy obiektów (lub tablicy ciągów), a nie tablicy int. Wystąpi błąd „połączenie jest niejednoznaczne”.
LarryBud
2

Zgadzam się z wyrażeniem lambda dotyczącym czytelności i łatwości konserwacji, ale nie zawsze będzie to najlepsza opcja. Wadą stosowania zarówno podejścia IEnumerable / ToArray, jak i StringBuilder jest to, że muszą one dynamicznie rozwijać listę elementów lub znaków, ponieważ nie wiedzą, ile miejsca będzie potrzebne na końcowy ciąg.

W rzadkich przypadkach, w których szybkość jest ważniejsza niż zwięzłość, bardziej efektywne są poniższe czynności.

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
źródło
2
ints.Aggregate("", ( str, n ) => str +","+ n ).Substring(1);

Pomyślałem też, że istnieje prostszy sposób. Nie wiesz o wydajności, ktoś ma jakiś (teoretyczny) pomysł?

unieważnić
źródło
To rozwiązanie dałoby ci „1,2,3,4,5”.
Sarin
Dzięki, dodałem, Substring(1)żeby to naprawić (to było z pamięci).
nieważne
2

W .NET 4.0 łączenie ciągów ma przeciążenie dla params object[], więc jest tak proste, jak:

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

przykład

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

W .NET 2.0 jest to trochę trudniejsze, ponieważ nie ma takiego przeciążenia. Potrzebujesz więc własnej ogólnej metody:

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;
}

W .NET 3.5 można używać metod rozszerzających:

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;
    }

}

Możesz więc użyć metody rozszerzenia JoinArray.

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

Możesz również użyć tej metody rozszerzenia w .NET 2.0, jeśli dodasz ExtensionAttribute do swojego kodu:

// 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
źródło
0

zapomnij o .net 3.5 i użyj następnego kodu w .net core

var result = string.Join(",", ints);
UkrGuru
źródło