Genel listeyi CSV dizesine dönüştürme


139

Tamsayı değerleri (Liste) listesi var ve virgülle sınırlanmış değerler dizesi oluşturmak istiyorum. Bu listedeki tüm öğeler tek bir virgülle ayrılmış listeye çıkar.

Düşüncelerim ... 1. listeyi bir yönteme aktar. 2. Listeyi yinelemek ve virgül eklemek için stringbuilder kullanın 3. Son karakteri test edin ve virgülse silin.

Düşüncelerin nelerdir? En iyi yol bu mu?

Gelecekte yalnızca tam sayıları (geçerli planımı) değil, dizeleri, uzunları, çiftleri, boolsları vb. İşlemek istersem kodum nasıl değişecekti? Sanırım herhangi bir tür listeyi kabul et.

Yanıtlar:


243

Framework'ün bizim için zaten yaptığı şey şaşırtıcı.

List<int> myValues;
string csv = String.Join(",", myValues.Select(x => x.ToString()).ToArray());

Genel durum için:

IEnumerable<T> myList;
string csv = String.Join(",", myList.Select(x => x.ToString()).ToArray());

Gördüğünüz gibi, etkili bir şekilde farklı değil. Virgül içeriyorsa gerçekte x.ToString()tırnak işaretleri (ör. "\"" + x.ToString() + "\"") Sarmanız gerekebilir x.ToString().

Bunun hafif bir varyantı hakkında ilginç bir okuma için: Eric Lippert'in blogundaki Virgül Kavga'ya bakın .

Not: Bu, .NET 4.0 resmi olarak yayımlanmadan önce yazılmıştır. Şimdi sadece söyleyebiliriz

IEnumerable<T> sequence;
string csv = String.Join(",", sequence);

aşırı yük kullanarak String.Join<T>(string, IEnumerable<T>). Bu yöntem otomatik olarak her eleman yansıtacaktır xiçin x.ToString().


List<int>bir Selectşey eksik sürece çerçeve 3.5 yöntem yok .
ajeh

2
@ajeh: Muhtemelen bir usingifade eksik .
jason

Hangi spesifik ithalat?
ajeh

1
Deneyin System.Linq.Enumerable(ve elbette System.Core.dlltoplanmaya ihtiyacınız olacak , ancak muhtemelen zaten buna sahipsiniz). Görüyorsunuz, List<int> asla sahip Selectbir yöntem olarak. Aksine, bunun bir örneği olan bir genişletme yöntemi olarak System.Linq.Enumerabletanımlar . Bu nedenle, bu uzantı yöntemini almak için ithalatlarınızda ihtiyacınız var . SelectIEnumerable<T>List<int>System.Linq.Enumerable
jason

Sayısal değerlerle uğraşıyorsanız ve virgül bir sorunsa (yerel ayara bağlı olarak), alternatiflerden biri x.ToString(CultureInfo.InvariantCulture). Bu, ondalık ayırıcı olarak nokta kullanır.
heltonbiker

15

3.5'te hala bunu başardım. Çok daha basit ve lambda gerekmez.

String.Join(",", myList.ToArray<string>());

ToArray()yöntemi bir List<int>şey eksik sürece çerçeve 3.5 tür argüman ile kullanılamaz.
ajeh

Parlak. Alt ToString () kullanıldığından ToArray <dizgisine> gerek yoktur.
Christian

11

Herhangi bir IEnumerable'da çağırabileceğiniz bir uzantı yöntemi oluşturabilirsiniz:

public static string JoinStrings<T>(
    this IEnumerable<T> values, string separator)
{
    var stringValues = values.Select(item =>
        (item == null ? string.Empty : item.ToString()));
    return string.Join(separator, stringValues.ToArray());
}

Ardından, orijinal listedeki yöntemi çağırabilirsiniz:

string commaSeparated = myList.JoinStrings(", ");

7

Kullanabilirsiniz String.Join.

String.Join(
  ",",
  Array.ConvertAll(
     list.ToArray(),
     element => element.ToString()
  )
);

ConvertAllBuraya çağrıda genel tip parametrelerini belirtmeye gerek yoktur - her ikisi de intve stringçıkartılacaktır.
Pavel Minaev

1
Yerine yapmanın Array.ConvertAll(...' you can just do list.ConvertAll (e => e.ToString ()). ToArray) `, sadece daha az yazmaya.
David

string.Join (",", liste); iyi yapacağız :)
Christian

6

Herhangi bir gövde , dize listesi yerine özel sınıf nesnelerinin listesini dönüştürmek istiyorsa, sınıfınızın ToString yöntemini sınıfınızın csv satır temsiliyle geçersiz kılın.

Public Class MyClass{
   public int Id{get;set;}
   public String PropertyA{get;set;}
   public override string ToString()
   {
     return this.Id+ "," + this.PropertyA;
   }
}

Daha sonra bu kod listesini başlık sütunuyla CSV'ye dönüştürmek için aşağıdaki kod kullanılabilir

string csvHeaderRow = String.Join(",", typeof(MyClass).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(x => x.Name).ToArray<string>()) + Environment.NewLine;
string csv= csvHeaderRow + String.Join(Environment.NewLine, MyClass.Select(x => x.ToString()).ToArray());

myExampleCollection.Select yerine MyClass.Select
Piotr Ferenc

5

@Frank tarafından verilen bağlantıdaki kod olarak olarak bir .NET Genel Listesinden bir CSV Dosyası Oluşturun, Ben ondan kurtulmak için kodu değiştirdim I ile her satırı bitirme küçük bir sorun vardı .

/// <summary>
/// Creates the CSV from a generic list.
/// </summary>;
/// <typeparam name="T"></typeparam>;
/// <param name="list">The list.</param>;
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>;
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath)
{
    if (list == null || list.Count == 0) return;

    //get type from 0th member
    Type t = list[0].GetType();
    string newLine = Environment.NewLine;

    if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath));

    if (!File.Exists(csvCompletePath)) File.Create(csvCompletePath);

    using (var sw = new StreamWriter(csvCompletePath))
    {
        //make a new instance of the class name we figured out to get its props
        object o = Activator.CreateInstance(t);
        //gets all properties
        PropertyInfo[] props = o.GetType().GetProperties();

        //foreach of the properties in class above, write out properties
        //this is the header row
        sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);

        //this acts as datarow
        foreach (T item in list)
        {
            //this acts as datacolumn
            var row = string.Join(",", props.Select(d => item.GetType()
                                                            .GetProperty(d.Name)
                                                            .GetValue(item, null)
                                                            .ToString())
                                                    .ToArray());
            sw.Write(row + newLine);

        }
    }
}

Ek bilgi: İşlem, başka bir işlem tarafından kullanıldığından 'c: \ temp \ matchingMainWav.csv' dosyasına erişemiyor. klasör devvar, ama dosya değil ... ben doğru kullanmıyorum?
Tom Stickel

File.Create yöntemi dosyayı oluşturur ve dosyada bir FileStream açar. Yani dosyanız zaten açık. Dosyaya gerçekten ihtiyacınız yok. Yöntem oluştur:
David

Herhangi bir özellik null olursa, bunun bir yolu var mı?
Daniel Jackson

@DanielJackson Bu ifadede bir nerede cümlesi yazabilirsiniz sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);Test edilmedi ancak ne elde etmeye çalıştığınızı bilmiyorum
Ali Umair

4

Bu konuyu derinlemesine açıklıyorum yazıda . Kodu buraya kısa açıklamalarla yapıştıracağım.

İşte üstbilgi satırını oluşturan yöntem. Özellik adlarını sütun adları olarak kullanır.

private static void CreateHeader<T>(List<T> list, StreamWriter sw)
    {
        PropertyInfo[] properties = typeof(T).GetProperties();
        for (int i = 0; i < properties.Length - 1; i++)
        {
            sw.Write(properties[i].Name + ",");
        }
        var lastProp = properties[properties.Length - 1].Name;
        sw.Write(lastProp + sw.NewLine);
    }

Bu yöntem tüm değer satırlarını oluşturur

private static void CreateRows<T>(List<T> list, StreamWriter sw)
    {
        foreach (var item in list)
        {
            PropertyInfo[] properties = typeof(T).GetProperties();
            for (int i = 0; i < properties.Length - 1; i++)
            {
                var prop = properties[i];
                sw.Write(prop.GetValue(item) + ",");
            }
            var lastProp = properties[properties.Length - 1];
            sw.Write(lastProp.GetValue(item) + sw.NewLine);
        }
    }

Ve işte onları bir araya getiren ve gerçek dosyayı oluşturan yöntem.

public static void CreateCSV<T>(List<T> list, string filePath)
    {
        using (StreamWriter sw = new StreamWriter(filePath))
        {
            CreateHeader(list, sw);
            CreateRows(list, sw);
        }
    }

1
Bu çok iyi çalışıyor. Bu, sınırlayıcıyı parametre olarak iletmek için geliştirdim, böylece her türlü sınırlandırılmış dosya oluşturulabilir. Metin virgül içeriyorsa CSV'lerle uğraşmak acı vericidir, bu yüzden |geliştirilmiş sürümü kullanarak sınırlandırılmış dosyalar oluştururum . Teşekkürler!
Shiva


3

Güzel ve basit bir uzatma yöntemini seviyorum

 public static string ToCsv(this List<string> itemList)
         {
             return string.Join(",", itemList);
         }

Ardından, orijinal listedeki yöntemi çağırabilirsiniz:

string CsvString = myList.ToCsv();

Diğer önerilere göre daha temiz ve kolay okunur.


2

String.Join ile ilgili sorun, değerde zaten var olan bir virgül örneğini işlememenizdir. Bir virgül olduğunda, Tırnaklar içindeki değeri çevreler ve varolan tüm Tırnakları çift Tırnaklarla değiştirirsiniz.

String.Join(",",{"this value has a , in it","This one doesn't", "This one , does"});

Bkz. CSV Modülü


2

CsvHelper kütüphanesi Nuget'te çok popülerdir. https://github.com/JoshClose/CsvHelper/wiki/Basics

CsvHelper kullanmak gerçekten çok kolay. Varsayılan ayarları, en yaygın senaryolar için ayarlanmıştır.

İşte küçük bir kurulum verisi.

Actors.csv:

Id,FirstName,LastName  
1,Arnold,Schwarzenegger  
2,Matt,Damon  
3,Christian,Bale

Actor.cs (bir aktörü temsil eden özel sınıf nesnesi):

public class Actor
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

CsvReader kullanarak CSV dosyasını okuma:

var csv = new CsvReader( new StreamReader( "Actors.csv" ) );

var actorsList = csv.GetRecords ();

Bir CSV dosyasına yazma.

using (var csv = new CsvWriter( new StreamWriter( "Actors.csv" ) )) 
{
    csv.WriteRecords( actorsList );
}

2

Her ne sebeple olursa olsun, @AliUmair düzenlemeyi çalışmayan kodunu düzelten cevabına geri döndürdü, bu yüzden dosya erişim hatası olmayan ve null nesne özellik değerlerini düzgün işleyen çalışma sürümü:

/// <summary>
/// Creates the CSV from a generic list.
/// </summary>;
/// <typeparam name="T"></typeparam>;
/// <param name="list">The list.</param>;
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>;
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath)
{
    if (list == null || list.Count == 0) return;

    //get type from 0th member
    Type t = list[0].GetType();
    string newLine = Environment.NewLine;

    if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath));

    using (var sw = new StreamWriter(csvCompletePath))
    {
        //make a new instance of the class name we figured out to get its props
        object o = Activator.CreateInstance(t);
        //gets all properties
        PropertyInfo[] props = o.GetType().GetProperties();

        //foreach of the properties in class above, write out properties
        //this is the header row
        sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);

        //this acts as datarow
        foreach (T item in list)
        {
            //this acts as datacolumn
            var row = string.Join(",", props.Select(d => $"\"{item.GetType().GetProperty(d.Name).GetValue(item, null)?.ToString()}\"")
                                                    .ToArray());
            sw.Write(row + newLine);

        }
    }
}


1

Genel amaçlı bir ToCsv () genişletme yöntemi:

  • Int16 / 32/64, kayan, çift, ondalık ve ToString'i destekleyen her şeyi destekler ()
  • İsteğe bağlı özel birleştirme ayırıcı
  • İsteğe bağlı özel seçici
  • İsteğe bağlı boş / boş kullanım özellikleri (* Opt () aşırı yükleri)

Kullanım örnekleri:

"123".ToCsv() // "1,2,3"
"123".ToCsv(", ") // "1, 2, 3"
new List<int> { 1, 2, 3 }.ToCsv() // "1,2,3"

new List<Tuple<int, string>> 
{ 
    Tuple.Create(1, "One"), 
    Tuple.Create(2, "Two") 
}
.ToCsv(t => t.Item2);  // "One,Two"

((string)null).ToCsv() // throws exception
((string)null).ToCsvOpt() // ""
((string)null).ToCsvOpt(ReturnNullCsv.WhenNull) // null

uygulama

/// <summary>
/// Specifies when ToCsv() should return null.  Refer to ToCsv() for IEnumerable[T]
/// </summary>
public enum ReturnNullCsv
{
    /// <summary>
    /// Return String.Empty when the input list is null or empty.
    /// </summary>
    Never,

    /// <summary>
    /// Return null only if input list is null.  Return String.Empty if list is empty.
    /// </summary>
    WhenNull,

    /// <summary>
    /// Return null when the input list is null or empty
    /// </summary>
    WhenNullOrEmpty,

    /// <summary>
    /// Throw if the argument is null
    /// </summary>
    ThrowIfNull
}   

/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>        
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsv<T>(
    this IEnumerable<T> values,            
    string joinSeparator = ",")
{
    return ToCsvOpt<T>(values, null /*selector*/, ReturnNullCsv.ThrowIfNull, joinSeparator);
}

/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="selector">An optional selector</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsv<T>(
    this IEnumerable<T> values,
    Func<T, string> selector,            
    string joinSeparator = ",") 
{
    return ToCsvOpt<T>(values, selector, ReturnNullCsv.ThrowIfNull, joinSeparator);
}

/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsvOpt<T>(
    this IEnumerable<T> values,
    ReturnNullCsv returnNullCsv = ReturnNullCsv.Never,
    string joinSeparator = ",")
{
    return ToCsvOpt<T>(values, null /*selector*/, returnNullCsv, joinSeparator);
}

/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="selector">An optional selector</param>
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsvOpt<T>(
    this IEnumerable<T> values, 
    Func<T, string> selector,
    ReturnNullCsv returnNullCsv = ReturnNullCsv.Never,
    string joinSeparator = ",")
{
    switch (returnNullCsv)
    {
        case ReturnNullCsv.Never:
            if (!values.AnyOpt())
                return string.Empty;
            break;

        case ReturnNullCsv.WhenNull:
            if (values == null)
                return null;
            break;

        case ReturnNullCsv.WhenNullOrEmpty:
            if (!values.AnyOpt())
                return null;
            break;

        case ReturnNullCsv.ThrowIfNull:
            if (values == null)
                throw new ArgumentOutOfRangeException("ToCsvOpt was passed a null value with ReturnNullCsv = ThrowIfNull.");
            break;

        default:
            throw new ArgumentOutOfRangeException("returnNullCsv", returnNullCsv, "Out of range.");
    }

    if (selector == null)
    {
        if (typeof(T) == typeof(Int16) || 
            typeof(T) == typeof(Int32) || 
            typeof(T) == typeof(Int64))
        {                   
            selector = (v) => Convert.ToInt64(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(decimal))
        {
            selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(float) ||
                typeof(T) == typeof(double))
        {
            selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            selector = (v) => v.ToString();
        }            
    }

    return String.Join(joinSeparator, values.Select(v => selector(v)));
}

public static string ToStringInvariantOpt(this Decimal? d)
{
    return d.HasValue ? d.Value.ToStringInvariant() : null;
}

public static string ToStringInvariant(this Decimal d)
{
    return d.ToString(CultureInfo.InvariantCulture);
}

public static string ToStringInvariantOpt(this Int64? l)
{
    return l.HasValue ? l.Value.ToStringInvariant() : null;
}

public static string ToStringInvariant(this Int64 l)
{
    return l.ToString(CultureInfo.InvariantCulture);
}

public static string ToStringInvariantOpt(this Int32? i)
{
    return i.HasValue ? i.Value.ToStringInvariant() : null;
}

public static string ToStringInvariant(this Int32 i)
{
    return i.ToString(CultureInfo.InvariantCulture);
}

public static string ToStringInvariantOpt(this Int16? i)
{
    return i.HasValue ? i.Value.ToStringInvariant() : null;
}

public static string ToStringInvariant(this Int16 i)
{
    return i.ToString(CultureInfo.InvariantCulture);
}

0

İşte benim uzantısı yöntemi, basitlik için bir dize döndürür ama benim uygulama dosyayı bir veri gölüne yazar.

Herhangi bir sınırlayıcı sağlar, dizeye tırnak ekler (sınırlayıcıyı içermeleri durumunda) ve fırsatlar boş ve boşluklu olur.

    /// <summary>
    /// A class to hold extension methods for C# Lists 
    /// </summary>
    public static class ListExtensions
    {
        /// <summary>
        /// Convert a list of Type T to a CSV
        /// </summary>
        /// <typeparam name="T">The type of the object held in the list</typeparam>
        /// <param name="items">The list of items to process</param>
        /// <param name="delimiter">Specify the delimiter, default is ,</param>
        /// <returns></returns>
        public static string ToCsv<T>(this List<T> items, string delimiter = ",")
        {
            Type itemType = typeof(T);
            var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);

            var csv = new StringBuilder();

            // Write Headers
            csv.AppendLine(string.Join(delimiter, props.Select(p => p.Name)));

            // Write Rows
            foreach (var item in items)
            {
                // Write Fields
                csv.AppendLine(string.Join(delimiter, props.Select(p => GetCsvFieldasedOnValue(p, item))));
            }

            return csv.ToString();
        }

        /// <summary>
        /// Provide generic and specific handling of fields
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        private static object GetCsvFieldasedOnValue<T>(PropertyInfo p, T item)
        {
            string value = "";

            try
            {
                value = p.GetValue(item, null)?.ToString();
                if (value == null) return "NULL";  // Deal with nulls
                if (value.Trim().Length == 0) return ""; // Deal with spaces and blanks

                // Guard strings with "s, they may contain the delimiter!
                if (p.PropertyType == typeof(string))
                {
                    value = string.Format("\"{0}\"", value);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return value;
        }
    }

Kullanımı:

 // Tab Delimited (TSV)
 var csv = MyList.ToCsv<MyClass>("\t");
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.