일반 목록을 CSV 문자열로 변환


139

정수 값 목록 (목록)이 있고 쉼표로 구분 된 값의 문자열을 생성하고 싶습니다. 이는 목록의 모든 항목이 단일 쉼표 제거 목록으로 출력됩니다.

내 생각은 ... 1. 목록을 메소드에 전달하십시오. 2. stringbuilder를 사용하여 목록을 반복하고 쉼표를 추가하십시오. 3. 마지막 문자를 테스트하고 쉼표 인 경우 삭제하십시오.

당신의 생각은 무엇입니까? 이것이 최선의 방법입니까?

앞으로 정수 (현재 계획)뿐만 아니라 문자열, long, double, bool 등을 처리하려면 코드가 어떻게 변경됩니까? 모든 유형의 목록을 허용한다고 생각합니다.

답변:


243

프레임 워크가 이미 우리를 위해하는 일이 놀랍습니다.

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

일반적인 경우 :

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

보시다시피 사실상 다르지 않습니다. 쉼표가 포함 된 경우 실제로 x.ToString()따옴표로 묶어야 할 수도 있습니다 (예 :)."\"" + x.ToString() + "\""x.ToString()

약간의 변형에 대한 흥미로운 내용 은 Eric Lippert의 블로그에서 Comma Quibbling 을 참조하십시오 .

참고 : 이것은 .NET 4.0이 공식적으로 출시되기 전에 작성되었습니다. 이제 우리는 말할 수 있습니다

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

과부하 사용 String.Join<T>(string, IEnumerable<T>). 이 방법은 각 요소 x를에 자동으로 투사 합니다 x.ToString().


List<int>Select내가 빠진 것이 아니라면 프레임 워크 3.5에 메소드가 없습니다 .
ajeh

2
@ajeh : 당신은 아마도 using진술을 잃어 버렸을 것입니다 .
Jason

어떤 특정 수입품?
ajeh

1
시도하십시오 System.Linq.Enumerable(물론 System.Core.dll어셈블리 가 필요 하지만 아마도 이미 가지고 있습니다). 당신은 List<int> 결코Select 방법으로 가지고 있지 않습니다 참조하십시오 . 오히려에 대한 확장 방법으로 System.Linq.Enumerable정의 Select되며 IEnumerable<T>List<int>중 하나입니다. 따라서이 System.Linq.Enumerable확장 방법을 선택하려면 가져 오기에서 필요 합니다.
Jason

로케일에 따라 숫자 값을 처리하고 쉼표에 문제가있는 경우 한 가지 대안은 x.ToString(CultureInfo.InvariantCulture)입니다. 마침표를 소수점 구분 기호로 사용합니다.
heltonbiker

15

3.5에서는 여전히이 작업을 수행 할 수있었습니다. 훨씬 간단하고 람다가 필요하지 않습니다.

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

ToArray()List<int>내가 누락 된 경우가 아니라면 프레임 워크 3.5에서 type 인수와 함께 메서드를 사용할 수 없습니다.
ajeh

훌륭한. 자식 ToString ()이 사용되므로 ToArray <string>이 ​​필요하지 않습니다.
Christian

11

IEnumerable에서 호출 할 수있는 확장 메서드를 만들 수 있습니다.

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

그런 다음 원래 목록에서 메소드를 호출하면됩니다.

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

7

사용할 수 있습니다 String.Join.

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

필요가에 호출 제네릭 형식 매개 변수를 지정할 수 없습니다 ConvertAll여기 - 모두 intstring추정됩니다.
Pavel Minaev

1
Array.ConvertAll(...' you can just do list.ConvertAll (e => e.ToString ()). ToArray)` 를 수행하는 대신 입력하는 것이 적습니다.
David

string.Join ( ",", 목록); 잘 할 것입니다 :)
Christian

6

본문 에서 문자열 목록 대신 사용자 정의 클래스 객체 목록을 변환하려는 경우 클래스의 csv 행 표현으로 클래스의 ToString 메소드를 대체하십시오.

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

그런 다음 헤더 코드가있는이 클래스 목록을 CSV로 변환하는 데 다음 코드를 사용할 수 있습니다

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 대신 MyClass.Select
Piotr Ferenc

5

@Frank가 제공 한 링크의 코드로 .NET Generic List에서 CSV 파일을 만들면, 코드를 수정하여 모든 줄을 끝내는 약간의 문제 가 발생했습니다.

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

        }
    }
}

추가 정보 : 프로세스가 다른 프로세스에서 사용 중이므로 'c : \ temp \ matchingMainWav.csv'파일에 액세스 할 수 없습니다. 폴더 dev가 존재하지만 파일이 아닙니다 ... 나는 그 권리를 사용하고 있지 않습니까?
Tom Stickel

File.Create 메소드는 파일을 작성하고 파일에서 FileStream을 엽니 다. 따라서 파일이 이미 열려 있습니다. 실제로 파일이 필요하지 않습니다. 전혀 메소드를 작성하지 마십시오.
David

속성이 null 인 경우 그 주위에 방법이 있습니까?
Daniel Jackson

@DanielJackson이 문장에 where 절을 작성할 수 있습니다. sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);테스트되지 않았지만 무엇을 성취하려고하는지 모릅니다
Ali Umair

4

게시물 에서 자세히 설명합니다 . 간단한 설명과 함께 여기에 코드를 붙여 넣습니다.

헤더 행을 만드는 방법은 다음과 같습니다. 속성 이름을 열 이름으로 사용합니다.

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

이 방법은 모든 값 행을 만듭니다.

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

그리고 그것들을 모아서 실제 파일을 만드는 방법이 있습니다.

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

1
이것은 매우 잘 작동합니다. 구분 기호를 매개 변수로 전달하도록이를 개선 했으므로 모든 유형의 구분 파일을 생성 할 수 있습니다. 텍스트에 쉼표가 포함되어 있으면 CSV를 처리하기가 어려워 |개선 된 버전을 사용하여 구분 된 파일을 생성 합니다. 감사!
시바


3

나는 멋진 간단한 확장 방법을 좋아한다

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

그런 다음 원래 목록에서 메소드를 호출하면됩니다.

string CsvString = myList.ToCsv();

다른 제안보다 깨끗하고 읽기 쉽습니다.


2

String.Join의 문제점은 값에 이미 존재하는 쉼표를 처리하지 않는다는 것입니다. 쉼표가 있으면 따옴표로 값을 묶고 기존의 모든 따옴표를 큰 따옴표로 바꿉니다.

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

CSV 모듈 참조


2

CsvHelper 라이브러리는 Nuget에서 매우 인기가 있습니다. https://github.com/JoshClose/CsvHelper/wiki/Basics

CsvHelper를 사용하는 것은 정말 쉽습니다. 기본 설정은 가장 일반적인 시나리오에 대해 설정됩니다.

다음은 약간의 설정 데이터입니다.

Actors.csv :

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

Actor.cs (액터를 나타내는 사용자 정의 클래스 객체) :

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

CsvReader를 사용하여 CSV 파일 읽기 :

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

var actorsList = csv.GetRecords ();

CSV 파일에 쓰는 중입니다.

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

2

어떤 이유로 든 @AliUmair는 실행되지 않은 코드를 수정하는 답변으로 편집 내용을 되돌 렸습니다. 따라서 파일 액세스 오류가없고 null 객체 속성 값을 올바르게 처리하는 작업 버전이 있습니다.

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

범용 ToCsv () 확장 메소드 :

  • Int16 / 32 / 64, float, double, decimal 및 ToString ()을 지원하는 모든 것을 지원합니다
  • 선택적 사용자 지정 조인 구분 기호
  • 선택적인 커스텀 셀렉터
  • 선택적 null / 빈 처리 사양 (* Opt () 오버로드)

사용 예 :

"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

이행

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

다음은 확장 방법입니다. 간단하게 문자열을 반환하지만 구현시 파일을 데이터 레이크에 씁니다.

모든 구분자를 제공하고 문자열에 따옴표를 추가하고 (구분자가 포함 된 경우) 널과 공백을 처리합니다.

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

용법:

 // Tab Delimited (TSV)
 var csv = MyList.ToCsv<MyClass>("\t");
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.