문제

일반적인 생성 방법 CSV 문자열(의사 코드):

  1. C#의 StringBuilder와 같은 CSV 컨테이너 개체를 만듭니다.
  2. 각 문자열 뒤에 쉼표를 추가하여 추가하려는 문자열을 반복합니다.
  3. 루프 다음에 마지막 불필요한 쉼표를 제거하십시오.

코드 샘플:

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    foreach (Contact c in contactList)
    {
        sb.Append(c.Name + ",");
    }

    sb.Remove(sb.Length - 1, 1);
    //sb.Replace(",", "", sb.Length - 1, 1)

    return sb.ToString();
}

컨테이너가 비어 있는지 확인하여 쉼표를 추가하는 아이디어가 마음에 들지만, 발생할 때마다 문자열 길이를 확인해야 하므로 더 많은 처리를 의미하지 않습니까?

마지막 쉼표를 제거하는 더 쉽고, 더 깨끗하고, 더 효율적인 방법이 있어야 한다고 생각합니다.어떤 아이디어가 있나요?

도움이 되었습니까?

해결책

당신은 사용할 수 있습니다 LINQ에서 개체로:

string [] strings = contactList.Select(c => c.Name).ToArray();
string csv = string.Join(",", strings);

분명히 한 줄로 모두 수행할 수 있지만 두 줄로 하면 좀 더 명확해집니다.

다른 팁

귀하의 코드가 실제로 호환되지 않습니다 전체 CSV 형식.쉼표, 선행/후행 공백, 탭, 줄 바꿈 또는 따옴표가 없는 데이터에서 CSV를 생성하는 경우에는 문제가 없습니다.그러나 대부분의 실제 데이터 교환 시나리오에서는 전체 구현이 필요합니다.

적절한 CSV를 생성하려면 다음을 사용할 수 있습니다.

public static String EncodeCsvLine(params String[] fields)
{
    StringBuilder line = new StringBuilder();

    for (int i = 0; i < fields.Length; i++)
    {
        if (i > 0)
        {
            line.Append(DelimiterChar);
        }

        String csvField = EncodeCsvField(fields[i]);
        line.Append(csvField);
    }

    return line.ToString();
}

static String EncodeCsvField(String field)
{
    StringBuilder sb = new StringBuilder();
    sb.Append(field);

    // Some fields with special characters must be embedded in double quotes
    bool embedInQuotes = false;

    // Embed in quotes to preserve leading/tralining whitespace
    if (sb.Length > 0 && 
        (sb[0] == ' ' || 
         sb[0] == '\t' ||
         sb[sb.Length-1] == ' ' || 
         sb[sb.Length-1] == '\t' ))
    {
        embedInQuotes = true;
    }

    for (int i = 0; i < sb.Length; i++)
    {
        // Embed in quotes to preserve: commas, line-breaks etc.
        if (sb[i] == DelimiterChar || 
            sb[i]=='\r' || 
            sb[i]=='\n' || 
            sb[i] == '"') 
        { 
            embedInQuotes = true;
            break;
        }
    }

    // If the field itself has quotes, they must each be represented 
    // by a pair of consecutive quotes.
    sb.Replace("\"", "\"\"");

    String rv = sb.ToString();

    if (embedInQuotes)
    {
        rv = "\"" + rv + "\"";
    }

    return rv;
}

세계에서 가장 효율적인 코드는 아닐 수도 있지만 테스트를 거쳤습니다.실제 세계는 빠른 샘플 코드에 비해 형편없습니다 :)

오픈 소스 CSV 라이브러리 중 하나를 사용해 보는 것은 어떨까요?

너무 단순해 보이는 것이 너무 지나친 것처럼 들리겠지만, 주석과 코드 조각에서 알 수 있듯이 눈에 보이는 것 이상의 것이 있습니다.전체 CSV 규정 준수를 처리하는 것 외에도 결국에는 CSV 읽기 및 쓰기를 모두 처리하게 됩니다...파일 조작을 원할 수도 있습니다.

나는 사용했다 CSV 열기 이전에 내 프로젝트 중 하나에서 (그러나 선택할 수 있는 다른 프로젝트가 많이 있습니다).확실히 내 삶이 더 편해졌습니다.;)

우리의 오랜 친구 "for"를 잊지 마세요.foreach만큼 보기에는 좋지는 않지만 두 번째 요소에서 시작할 수 있다는 장점이 있습니다.

public string ReturnAsCSV(ContactList contactList)
{
    if (contactList == null || contactList.Count == 0)
        return string.Empty;

    StringBuilder sb = new StringBuilder(contactList[0].Name);

    for (int i = 1; i < contactList.Count; i++)
    {
        sb.Append(",");
        sb.Append(contactList[i].Name);
    }

    return sb.ToString();
}

Name 속성에 큰따옴표나 쉼표가 포함되어 있는지 테스트하고, 포함된 경우 적절하게 이스케이프 처리하는 "if"로 두 번째 Append를 래핑할 수도 있습니다.

대신 foreach 내부의 첫 번째 항목으로 쉼표를 추가할 수 있습니다.

if (sb.Length > 0) sb.Append(",");

당신은 또한 배열을 만들 수 있습니다 c.이름 데이터 및 사용 문자열.조인 라인을 생성하는 방법.

public string ReturnAsCSV(ContactList contactList)
{
    List<String> tmpList = new List<string>();

    foreach (Contact c in contactList)
    {
        tmpList.Add(c.Name);
    }

    return String.Join(",", tmpList.ToArray());
}

이 방법은 다음과 같은 성능을 발휘하지 못할 수 있습니다. 스트링빌더 접근 방식이지만 확실히 더 깨끗해 보입니다.

또한 사용을 고려할 수도 있습니다. .CurrentCulture.TextInfo.ListSeparator 하드 코딩된 쉼표 대신 - 출력을 다른 응용 프로그램으로 가져오려는 경우 문제가 발생할 수 있습니다.ListSeparator는 문화에 따라 다를 수 있으며 MS Excel에서는 최소한 이 설정을 따릅니다.그래서:

return String.Join(
    System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator,
    tmpList.ToArray());

컨테이너가 비어 있는지 확인하여 쉼표를 추가하는 아이디어가 마음에 들지만, 발생할 때마다 문자열 길이를 확인해야 하므로 더 많은 처리를 의미하지 않습니까?

성급하게 최적화하고 있으므로 성능 저하가 미미할 것입니다.

생각해보면, 필드 값에서 쉼표와 따옴표(")를 처리해야 한다는 점을 기억하세요. 그렇지 않으면 CSV 파일이 소비자 읽기에 방해가 될 수 있습니다.

다른 사람이 유용하다고 생각할 경우를 대비해 소규모 클래스를 작성했습니다.

public class clsCSVBuilder
{
    protected int _CurrentIndex = -1;
    protected List<string> _Headers = new List<string>();
    protected List<List<string>> _Records = new List<List<string>>();
    protected const string SEPERATOR = ",";

    public clsCSVBuilder() { }

    public void CreateRow()
    {
        _Records.Add(new List<string>());
        _CurrentIndex++;
    }

    protected string _EscapeString(string str)
    {
        return string.Format("\"{0}\"", str.Replace("\"", "\"\"")
                                            .Replace("\r\n", " ")
                                            .Replace("\n", " ")
                                            .Replace("\r", " "));
    }

    protected void _AddRawString(string item)
    {
        _Records[_CurrentIndex].Add(item);
    }

    public void AddHeader(string name)
    {
        _Headers.Add(_EscapeString(name));
    }

    public void AddRowItem(string item)
    {
        _AddRawString(_EscapeString(item));
    }

    public void AddRowItem(int item)
    {
        _AddRawString(item.ToString());
    }

    public void AddRowItem(double item)
    {
        _AddRawString(item.ToString());
    }

    public void AddRowItem(DateTime date)
    {
        AddRowItem(date.ToShortDateString());
    }

    public static string GenerateTempCSVPath()
    {
        return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().ToLower().Replace("-", "") + ".csv");
    }

    protected string _GenerateCSV()
    {
        StringBuilder sb = new StringBuilder();

        if (_Headers.Count > 0)
        {
            sb.AppendLine(string.Join(SEPERATOR, _Headers.ToArray()));
        }

        foreach (List<string> row in _Records)
        {
            sb.AppendLine(string.Join(SEPERATOR, row.ToArray()));
        }

        return sb.ToString();
    }

    public void SaveAs(string path)
    {
        using (StreamWriter sw = new StreamWriter(path))
        {
            sw.Write(_GenerateCSV());
        }
    }
}

나는 이전에 이 방법을 사용한 적이 있습니다.StringBuilder의 Length 속성은 읽기 전용이 아니므로 1을 빼면 마지막 문자가 잘립니다.하지만 길이를 0보다 작게 설정하는 것은 오류이기 때문에 시작하려면 길이가 0이 아닌지 확인해야 합니다(목록이 비어 있는 경우 발생함).

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();

    foreach (Contact c in contactList)       
    { 
        sb.Append(c.Name + ",");       
    }

    if (sb.Length > 0)  
        sb.Length -= 1;

    return sb.ToString();  
}

나는 사용한다 CSVHelper - 한 번에 하나의 요소를 준수하는 CSV 스트림을 생성하거나 클래스를 사용자 정의할 수 있는 훌륭한 오픈 소스 라이브러리입니다.

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    using (StringWriter stringWriter = new StringWriter(sb))
    {
        using (var csvWriter = new CsvHelper.CsvWriter(stringWriter))
        {
            csvWriter.Configuration.HasHeaderRecord = false;
            foreach (Contact c in contactList)
            {
                csvWriter.WriteField(c.Name);
            }
        }
    }
    return sb.ToString();
}

또는 매핑하면 다음과 같습니다. csvWriter.WriteRecords<ContactList>(contactList);

좀 다듬어 보는 건 어때요?

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();

    foreach (Contact c in contactList)
    {
        sb.Append(c.Name + ",");
    }

    return sb.ToString().Trim(',');
}

첫 번째 항목에 있는지 추적하고 쉼표만 추가하는 것은 어떻습니까? ~ 전에 첫 번째 항목이 아닌 경우 해당 항목입니다.

public string ReturnAsCSV(ContactList contactList)
{
    StringBuilder sb = new StringBuilder();
    bool isFirst = true;

    foreach (Contact c in contactList) {
        if (!isFirst) { 
          // Only add comma before item if it is not the first item
          sb.Append(","); 
        } else {
          isFirst = false;
        }

        sb.Append(c.Name);
    }

    return sb.ToString();
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top