Converting a list to a CSV string












6












$begingroup$


I want to be able to convert a list of objects into a string csv format. I've written this extension method below but have a feeling I'm missing something as this seems like potentially a common thing to want to do.



private static readonly char csvChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return string.Empty;
}

var builder = new StringBuilder();
var items = from item in source.Select(getItem)
where item != null
select item.ToString();

foreach (var str in items)
{
if (str.IndexOfAny(csvChars) > 0)
{
builder.Append(""").Append(str).Append(""").Append(", ");
}
else
{
builder.Append(str).Append(", ");
}
}

var csv = builder.ToString();

return csv.Length > 0 ? csv.TrimEnd(", ".ToCharArray()) : csv;
}


Is there anything I can do to improve this or refactor to a more elegant or working solution. Or even an existing method out there already that I may have missed.



UPDATE: Updated to take into account quotations as per Jesse comments below.










share|improve this question











$endgroup$












  • $begingroup$
    You are excluding null values. Shouldn't a null value still be represented in the CSV output? Depends on what you're using this for, I suppose.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 17:48
















6












$begingroup$


I want to be able to convert a list of objects into a string csv format. I've written this extension method below but have a feeling I'm missing something as this seems like potentially a common thing to want to do.



private static readonly char csvChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return string.Empty;
}

var builder = new StringBuilder();
var items = from item in source.Select(getItem)
where item != null
select item.ToString();

foreach (var str in items)
{
if (str.IndexOfAny(csvChars) > 0)
{
builder.Append(""").Append(str).Append(""").Append(", ");
}
else
{
builder.Append(str).Append(", ");
}
}

var csv = builder.ToString();

return csv.Length > 0 ? csv.TrimEnd(", ".ToCharArray()) : csv;
}


Is there anything I can do to improve this or refactor to a more elegant or working solution. Or even an existing method out there already that I may have missed.



UPDATE: Updated to take into account quotations as per Jesse comments below.










share|improve this question











$endgroup$












  • $begingroup$
    You are excluding null values. Shouldn't a null value still be represented in the CSV output? Depends on what you're using this for, I suppose.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 17:48














6












6








6





$begingroup$


I want to be able to convert a list of objects into a string csv format. I've written this extension method below but have a feeling I'm missing something as this seems like potentially a common thing to want to do.



private static readonly char csvChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return string.Empty;
}

var builder = new StringBuilder();
var items = from item in source.Select(getItem)
where item != null
select item.ToString();

foreach (var str in items)
{
if (str.IndexOfAny(csvChars) > 0)
{
builder.Append(""").Append(str).Append(""").Append(", ");
}
else
{
builder.Append(str).Append(", ");
}
}

var csv = builder.ToString();

return csv.Length > 0 ? csv.TrimEnd(", ".ToCharArray()) : csv;
}


Is there anything I can do to improve this or refactor to a more elegant or working solution. Or even an existing method out there already that I may have missed.



UPDATE: Updated to take into account quotations as per Jesse comments below.










share|improve this question











$endgroup$




I want to be able to convert a list of objects into a string csv format. I've written this extension method below but have a feeling I'm missing something as this seems like potentially a common thing to want to do.



private static readonly char csvChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return string.Empty;
}

var builder = new StringBuilder();
var items = from item in source.Select(getItem)
where item != null
select item.ToString();

foreach (var str in items)
{
if (str.IndexOfAny(csvChars) > 0)
{
builder.Append(""").Append(str).Append(""").Append(", ");
}
else
{
builder.Append(str).Append(", ");
}
}

var csv = builder.ToString();

return csv.Length > 0 ? csv.TrimEnd(", ".ToCharArray()) : csv;
}


Is there anything I can do to improve this or refactor to a more elegant or working solution. Or even an existing method out there already that I may have missed.



UPDATE: Updated to take into account quotations as per Jesse comments below.







c# csv






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Aug 24 '15 at 22:04









200_success

129k15152415




129k15152415










asked Jan 23 '12 at 20:47









drezadreza

5,9422241




5,9422241












  • $begingroup$
    You are excluding null values. Shouldn't a null value still be represented in the CSV output? Depends on what you're using this for, I suppose.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 17:48


















  • $begingroup$
    You are excluding null values. Shouldn't a null value still be represented in the CSV output? Depends on what you're using this for, I suppose.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 17:48
















$begingroup$
You are excluding null values. Shouldn't a null value still be represented in the CSV output? Depends on what you're using this for, I suppose.
$endgroup$
– Dr. Wily's Apprentice
Jan 24 '12 at 17:48




$begingroup$
You are excluding null values. Shouldn't a null value still be represented in the CSV output? Depends on what you're using this for, I suppose.
$endgroup$
– Dr. Wily's Apprentice
Jan 24 '12 at 17:48










3 Answers
3






active

oldest

votes


















6












$begingroup$

If your items contain a comma, carriage return or other special CSV character, you must delimit it with quotation marks.



namespace CsvStuff
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

internal static class CsvConstants
{
public static char TrimEnd { get; } = { ' ', ',' };

public static char CsvChars { get; } = { ',', '"', ' ', 'n', 'r' };
}

public abstract class CsvBase<T>
{
private readonly IEnumerable<T> values;

private readonly Func<T, object> getItem;

protected CsvBase(IEnumerable<T> values, Func<T, object> getItem)
{
this.values = values;
this.getItem = getItem;
}

public override string ToString()
{
var builder = new StringBuilder();

foreach (var item in
from element in this.values.Select(this.getItem)
where element != null
select element.ToString())
{
this.Build(builder, item).Append(", ");
}

return builder.ToString().TrimEnd(CsvConstants.TrimEnd);
}

protected abstract StringBuilder Build(StringBuilder builder, string item);
}

public class CsvBare<T> : CsvBase<T>
{
public CsvBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(item);
}
}

public sealed class CsvTrimBare<T> : CsvBare<T>
{
public CsvTrimBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvRfc4180<T> : CsvBase<T>
{
public CsvRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
item = item.Replace(""", """");
return item.IndexOfAny(CsvConstants.CsvChars) >= 0
? builder.Append(""").Append(item).Append(""")
: builder.Append(item);
}
}

public sealed class CsvTrimRfc4180<T> : CsvRfc4180<T>
{
public CsvTrimRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvAlwaysQuote<T> : CsvBare<T>
{
public CsvAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(""").Append(item.Replace(""", """")).Append(""");
}
}

public sealed class CsvTrimAlwaysQuote<T> : CsvAlwaysQuote<T>
{
public CsvTrimAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public static class CsvExtensions
{
public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem, Type csvProcessorType)
{
if ((source == null)
|| (getItem == null)
|| (csvProcessorType == null)
|| !csvProcessorType.IsSubclassOf(typeof(CsvBase<T>)))
{
return string.Empty;
}

return csvProcessorType
.GetConstructor(new { source.GetType(), getItem.GetType() })
?.Invoke(new object { source, getItem })
.ToString();
}

private static void Main()
{
var words = new { ",this", " is ", "a", "test", "Super, "luxurious" truck" };

Console.WriteLine(words.ToCsv(word => word, typeof(CsvAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvBare<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimBare<string>)));
Console.ReadLine();
}
}
}





share|improve this answer











$endgroup$













  • $begingroup$
    Excellent. Have edited my post to reflect your suggestions.
    $endgroup$
    – dreza
    Jan 23 '12 at 21:43










  • $begingroup$
    I made an edit to my code. IndexOfAny() should be >= 0 instead of > 0. Whoops!
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:22










  • $begingroup$
    There's a chance that I may not want to include quotations. Would you suggest just adding a boolean flag to the method parameter to handle this case.
    $endgroup$
    – dreza
    Jan 23 '12 at 22:34










  • $begingroup$
    Hm. Boolean flag would likely be the quickest, but it's not a very object-oriented solution. Let me noodle on that for a few and put something up.
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:37






  • 1




    $begingroup$
    @JesseC.Slicer +1 for the comprehensive example! But I think there's one more thing to add for it to be complete: if the value enclosed in quotes contains a quote itself, then that quote has to be doubled.
    $endgroup$
    – Cristian Lupascu
    Jan 24 '12 at 15:14



















3












$begingroup$

Here is my suggested version, which you should take as coming from a fellow learner.




  1. ToCsv is doing too much. I would be inclined to keep it very simple
    and perform only its very narrow responsibility: properly
    transforming a sequence of strings to a properly formatted csv
    string. Let the caller transform its sequence of arbitrary objects
    into strings.


  2. String.Join exists in the BCL, which is exactly suited to this
    purpose.


  3. Properly encodes the quote character




private class Program
{
private static void Main(string args)
{
int someInts = {1, 2, 3, 4, 10, 9, 8};
string someStrings = {"one", "two", "seven", "eight"};
string specialStrings = {"o'ne", ""tw"o", ",,three", "fo,ur", "five"};

Console.WriteLine(someInts.Select(s => s.ToString()).ToCsv());
Console.WriteLine(someStrings.ToCsv());
Console.WriteLine(specialStrings.ToCsv());
}
}

public static class CsvHelpers
{
private static readonly char csvSpecialChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv(this IEnumerable<string> source)
{
if (source == null)
{
return string.Empty;
}

var encodedStrings = from item in source
select EncodeCsvField(item);

return string.Join(",", encodedStrings);
}

private static string EncodeCsvField(string input)
{
string encodedString = input.Replace(""", """");
if (input.IndexOfAny(csvSpecialChars) >= 0)
{
encodedString = """ + encodedString + """;
}

return encodedString;
}
}





share|improve this answer









$endgroup$













  • $begingroup$
    I sort of wanted to stay away from putting the dependancy on the string object itself in the IEnumerable hence the use of generics. However didn't really know about the Join operater. Thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:13










  • $begingroup$
    My reasoning was that you either provide the function to project from your object to string or just do the projection (possibly through LINQ so you could defer the execution) at the call site. Seemed simpler to just do the projection at the call site if needed so that the extension method has only one responsibility. Cheers.
    $endgroup$
    – WorkerThread
    Jan 24 '12 at 21:13










  • $begingroup$
    Simple, short, clean, correct. Thanks.
    $endgroup$
    – mmdemirbas
    Mar 6 '12 at 11:29



















2












$begingroup$

A couple of things:




  1. Although you are using a StringBuilder (which is good in this case), you're generating a string for each row of data. If you're intending to use this to create multiple rows of data, then it will not quite as efficient as it could be. Maybe this is no big deal, although that might depend on how much data you're processing.


  2. Although I don't feel it's a major concern, I don't like stripping off the trailing comma. I always like to avoid messing up my output in the first place, rather than having additional code at the end that fixes the problem. One thing to realize, though, is that your StringBuilder generates an entire string in memory to contain the row of data, but when you call TrimEnd, I believe that this will create yet another string containing the entire row of data. Not very efficient.



I imagine that in most cases, this might be used to output to a file, or perhaps some other kind of stream. Therefore, it would make more sense to me to rewrite this functionality to write to a stream.



I've provided an alternate implementation below. The core functionality is in the WriteAsCsv method for the TextWriter. There are a few overloads to make this functionality easy to use for someone who has access to a FileStream object, a StringBuilder object, or just the original IEnumerable object.



This approach uses an enumerator in order to avoid the issue with the trailing comma.



    static void WriteCsvValue(System.IO.TextWriter writer, string str)
{
if (str.IndexOfAny(csvChars) > 0)
{
writer.Write(""");
writer.Write(str); // TODO: perform any necessary escaping
writer.Write(""");
}
else
{
writer.Write(str);
}
}

public static void WriteAsCsv<T>(this System.IO.TextWriter writer, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

var items = from item in source.Select(getItem)
where item != null
select item.ToString();

using (var enumerator = items.GetEnumerator())
{
if (enumerator.MoveNext())
{
WriteCsvValue(writer, enumerator.Current);

while (enumerator.MoveNext())
{
writer.Write(", ");
WriteCsvValue(writer, enumerator.Current);
}
}
}
}

public static void WriteAsCsv<T>(this System.IO.Stream stream, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StreamWriter(stream))
{
writer.WriteAsCsv(source, getItem);
}
}

public static void AppendAsCsv<T>(this StringBuilder builder, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StringWriter(builder))
{
writer.WriteAsCsv(source, getItem);
}
}

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
StringBuilder builder = new StringBuilder();
builder.AppendAsCsv(source, getItem);
return builder.ToString();
}



  • A StreamWriter is a TextWriter that wraps a Stream object.

  • A StringWriter is a TextWriter that wraps a StringBuilder object.


The gist of what I've done here is to decompose your original approach into separate pieces, making it easier to consume in multiple different ways.






share|improve this answer









$endgroup$













  • $begingroup$
    Although efficiency in the particular case I'm using it for isn't an issue as I'm not dealing with 100's of lines etc it's still something I didn't take into account. thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:15










  • $begingroup$
    @dreza if you don't care to use an enumerator (I know it feels clunky, at least until you get used to it), you could avoid having to strip off the trailing comma by prepending the comma: if(builder.Length>0)builder.Append(", ");
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:20










  • $begingroup$
    @dreza - See my answer on StackOverflow to a similar question.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:54











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f8228%2fconverting-a-list-to-a-csv-string%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























3 Answers
3






active

oldest

votes








3 Answers
3






active

oldest

votes









active

oldest

votes






active

oldest

votes









6












$begingroup$

If your items contain a comma, carriage return or other special CSV character, you must delimit it with quotation marks.



namespace CsvStuff
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

internal static class CsvConstants
{
public static char TrimEnd { get; } = { ' ', ',' };

public static char CsvChars { get; } = { ',', '"', ' ', 'n', 'r' };
}

public abstract class CsvBase<T>
{
private readonly IEnumerable<T> values;

private readonly Func<T, object> getItem;

protected CsvBase(IEnumerable<T> values, Func<T, object> getItem)
{
this.values = values;
this.getItem = getItem;
}

public override string ToString()
{
var builder = new StringBuilder();

foreach (var item in
from element in this.values.Select(this.getItem)
where element != null
select element.ToString())
{
this.Build(builder, item).Append(", ");
}

return builder.ToString().TrimEnd(CsvConstants.TrimEnd);
}

protected abstract StringBuilder Build(StringBuilder builder, string item);
}

public class CsvBare<T> : CsvBase<T>
{
public CsvBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(item);
}
}

public sealed class CsvTrimBare<T> : CsvBare<T>
{
public CsvTrimBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvRfc4180<T> : CsvBase<T>
{
public CsvRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
item = item.Replace(""", """");
return item.IndexOfAny(CsvConstants.CsvChars) >= 0
? builder.Append(""").Append(item).Append(""")
: builder.Append(item);
}
}

public sealed class CsvTrimRfc4180<T> : CsvRfc4180<T>
{
public CsvTrimRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvAlwaysQuote<T> : CsvBare<T>
{
public CsvAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(""").Append(item.Replace(""", """")).Append(""");
}
}

public sealed class CsvTrimAlwaysQuote<T> : CsvAlwaysQuote<T>
{
public CsvTrimAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public static class CsvExtensions
{
public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem, Type csvProcessorType)
{
if ((source == null)
|| (getItem == null)
|| (csvProcessorType == null)
|| !csvProcessorType.IsSubclassOf(typeof(CsvBase<T>)))
{
return string.Empty;
}

return csvProcessorType
.GetConstructor(new { source.GetType(), getItem.GetType() })
?.Invoke(new object { source, getItem })
.ToString();
}

private static void Main()
{
var words = new { ",this", " is ", "a", "test", "Super, "luxurious" truck" };

Console.WriteLine(words.ToCsv(word => word, typeof(CsvAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvBare<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimBare<string>)));
Console.ReadLine();
}
}
}





share|improve this answer











$endgroup$













  • $begingroup$
    Excellent. Have edited my post to reflect your suggestions.
    $endgroup$
    – dreza
    Jan 23 '12 at 21:43










  • $begingroup$
    I made an edit to my code. IndexOfAny() should be >= 0 instead of > 0. Whoops!
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:22










  • $begingroup$
    There's a chance that I may not want to include quotations. Would you suggest just adding a boolean flag to the method parameter to handle this case.
    $endgroup$
    – dreza
    Jan 23 '12 at 22:34










  • $begingroup$
    Hm. Boolean flag would likely be the quickest, but it's not a very object-oriented solution. Let me noodle on that for a few and put something up.
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:37






  • 1




    $begingroup$
    @JesseC.Slicer +1 for the comprehensive example! But I think there's one more thing to add for it to be complete: if the value enclosed in quotes contains a quote itself, then that quote has to be doubled.
    $endgroup$
    – Cristian Lupascu
    Jan 24 '12 at 15:14
















6












$begingroup$

If your items contain a comma, carriage return or other special CSV character, you must delimit it with quotation marks.



namespace CsvStuff
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

internal static class CsvConstants
{
public static char TrimEnd { get; } = { ' ', ',' };

public static char CsvChars { get; } = { ',', '"', ' ', 'n', 'r' };
}

public abstract class CsvBase<T>
{
private readonly IEnumerable<T> values;

private readonly Func<T, object> getItem;

protected CsvBase(IEnumerable<T> values, Func<T, object> getItem)
{
this.values = values;
this.getItem = getItem;
}

public override string ToString()
{
var builder = new StringBuilder();

foreach (var item in
from element in this.values.Select(this.getItem)
where element != null
select element.ToString())
{
this.Build(builder, item).Append(", ");
}

return builder.ToString().TrimEnd(CsvConstants.TrimEnd);
}

protected abstract StringBuilder Build(StringBuilder builder, string item);
}

public class CsvBare<T> : CsvBase<T>
{
public CsvBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(item);
}
}

public sealed class CsvTrimBare<T> : CsvBare<T>
{
public CsvTrimBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvRfc4180<T> : CsvBase<T>
{
public CsvRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
item = item.Replace(""", """");
return item.IndexOfAny(CsvConstants.CsvChars) >= 0
? builder.Append(""").Append(item).Append(""")
: builder.Append(item);
}
}

public sealed class CsvTrimRfc4180<T> : CsvRfc4180<T>
{
public CsvTrimRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvAlwaysQuote<T> : CsvBare<T>
{
public CsvAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(""").Append(item.Replace(""", """")).Append(""");
}
}

public sealed class CsvTrimAlwaysQuote<T> : CsvAlwaysQuote<T>
{
public CsvTrimAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public static class CsvExtensions
{
public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem, Type csvProcessorType)
{
if ((source == null)
|| (getItem == null)
|| (csvProcessorType == null)
|| !csvProcessorType.IsSubclassOf(typeof(CsvBase<T>)))
{
return string.Empty;
}

return csvProcessorType
.GetConstructor(new { source.GetType(), getItem.GetType() })
?.Invoke(new object { source, getItem })
.ToString();
}

private static void Main()
{
var words = new { ",this", " is ", "a", "test", "Super, "luxurious" truck" };

Console.WriteLine(words.ToCsv(word => word, typeof(CsvAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvBare<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimBare<string>)));
Console.ReadLine();
}
}
}





share|improve this answer











$endgroup$













  • $begingroup$
    Excellent. Have edited my post to reflect your suggestions.
    $endgroup$
    – dreza
    Jan 23 '12 at 21:43










  • $begingroup$
    I made an edit to my code. IndexOfAny() should be >= 0 instead of > 0. Whoops!
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:22










  • $begingroup$
    There's a chance that I may not want to include quotations. Would you suggest just adding a boolean flag to the method parameter to handle this case.
    $endgroup$
    – dreza
    Jan 23 '12 at 22:34










  • $begingroup$
    Hm. Boolean flag would likely be the quickest, but it's not a very object-oriented solution. Let me noodle on that for a few and put something up.
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:37






  • 1




    $begingroup$
    @JesseC.Slicer +1 for the comprehensive example! But I think there's one more thing to add for it to be complete: if the value enclosed in quotes contains a quote itself, then that quote has to be doubled.
    $endgroup$
    – Cristian Lupascu
    Jan 24 '12 at 15:14














6












6








6





$begingroup$

If your items contain a comma, carriage return or other special CSV character, you must delimit it with quotation marks.



namespace CsvStuff
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

internal static class CsvConstants
{
public static char TrimEnd { get; } = { ' ', ',' };

public static char CsvChars { get; } = { ',', '"', ' ', 'n', 'r' };
}

public abstract class CsvBase<T>
{
private readonly IEnumerable<T> values;

private readonly Func<T, object> getItem;

protected CsvBase(IEnumerable<T> values, Func<T, object> getItem)
{
this.values = values;
this.getItem = getItem;
}

public override string ToString()
{
var builder = new StringBuilder();

foreach (var item in
from element in this.values.Select(this.getItem)
where element != null
select element.ToString())
{
this.Build(builder, item).Append(", ");
}

return builder.ToString().TrimEnd(CsvConstants.TrimEnd);
}

protected abstract StringBuilder Build(StringBuilder builder, string item);
}

public class CsvBare<T> : CsvBase<T>
{
public CsvBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(item);
}
}

public sealed class CsvTrimBare<T> : CsvBare<T>
{
public CsvTrimBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvRfc4180<T> : CsvBase<T>
{
public CsvRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
item = item.Replace(""", """");
return item.IndexOfAny(CsvConstants.CsvChars) >= 0
? builder.Append(""").Append(item).Append(""")
: builder.Append(item);
}
}

public sealed class CsvTrimRfc4180<T> : CsvRfc4180<T>
{
public CsvTrimRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvAlwaysQuote<T> : CsvBare<T>
{
public CsvAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(""").Append(item.Replace(""", """")).Append(""");
}
}

public sealed class CsvTrimAlwaysQuote<T> : CsvAlwaysQuote<T>
{
public CsvTrimAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public static class CsvExtensions
{
public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem, Type csvProcessorType)
{
if ((source == null)
|| (getItem == null)
|| (csvProcessorType == null)
|| !csvProcessorType.IsSubclassOf(typeof(CsvBase<T>)))
{
return string.Empty;
}

return csvProcessorType
.GetConstructor(new { source.GetType(), getItem.GetType() })
?.Invoke(new object { source, getItem })
.ToString();
}

private static void Main()
{
var words = new { ",this", " is ", "a", "test", "Super, "luxurious" truck" };

Console.WriteLine(words.ToCsv(word => word, typeof(CsvAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvBare<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimBare<string>)));
Console.ReadLine();
}
}
}





share|improve this answer











$endgroup$



If your items contain a comma, carriage return or other special CSV character, you must delimit it with quotation marks.



namespace CsvStuff
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

internal static class CsvConstants
{
public static char TrimEnd { get; } = { ' ', ',' };

public static char CsvChars { get; } = { ',', '"', ' ', 'n', 'r' };
}

public abstract class CsvBase<T>
{
private readonly IEnumerable<T> values;

private readonly Func<T, object> getItem;

protected CsvBase(IEnumerable<T> values, Func<T, object> getItem)
{
this.values = values;
this.getItem = getItem;
}

public override string ToString()
{
var builder = new StringBuilder();

foreach (var item in
from element in this.values.Select(this.getItem)
where element != null
select element.ToString())
{
this.Build(builder, item).Append(", ");
}

return builder.ToString().TrimEnd(CsvConstants.TrimEnd);
}

protected abstract StringBuilder Build(StringBuilder builder, string item);
}

public class CsvBare<T> : CsvBase<T>
{
public CsvBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(item);
}
}

public sealed class CsvTrimBare<T> : CsvBare<T>
{
public CsvTrimBare(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvRfc4180<T> : CsvBase<T>
{
public CsvRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
item = item.Replace(""", """");
return item.IndexOfAny(CsvConstants.CsvChars) >= 0
? builder.Append(""").Append(item).Append(""")
: builder.Append(item);
}
}

public sealed class CsvTrimRfc4180<T> : CsvRfc4180<T>
{
public CsvTrimRfc4180(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public class CsvAlwaysQuote<T> : CsvBare<T>
{
public CsvAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return builder.Append(""").Append(item.Replace(""", """")).Append(""");
}
}

public sealed class CsvTrimAlwaysQuote<T> : CsvAlwaysQuote<T>
{
public CsvTrimAlwaysQuote(IEnumerable<T> values, Func<T, object> getItem) : base(values, getItem)
{
}

protected override StringBuilder Build(StringBuilder builder, string item)
{
return base.Build(builder, item.Trim());
}
}

public static class CsvExtensions
{
public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem, Type csvProcessorType)
{
if ((source == null)
|| (getItem == null)
|| (csvProcessorType == null)
|| !csvProcessorType.IsSubclassOf(typeof(CsvBase<T>)))
{
return string.Empty;
}

return csvProcessorType
.GetConstructor(new { source.GetType(), getItem.GetType() })
?.Invoke(new object { source, getItem })
.ToString();
}

private static void Main()
{
var words = new { ",this", " is ", "a", "test", "Super, "luxurious" truck" };

Console.WriteLine(words.ToCsv(word => word, typeof(CsvAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvBare<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimAlwaysQuote<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimRfc4180<string>)));
Console.WriteLine(words.ToCsv(word => word, typeof(CsvTrimBare<string>)));
Console.ReadLine();
}
}
}






share|improve this answer














share|improve this answer



share|improve this answer








edited 2 hours ago

























answered Jan 23 '12 at 21:26









Jesse C. SlicerJesse C. Slicer

11.4k2740




11.4k2740












  • $begingroup$
    Excellent. Have edited my post to reflect your suggestions.
    $endgroup$
    – dreza
    Jan 23 '12 at 21:43










  • $begingroup$
    I made an edit to my code. IndexOfAny() should be >= 0 instead of > 0. Whoops!
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:22










  • $begingroup$
    There's a chance that I may not want to include quotations. Would you suggest just adding a boolean flag to the method parameter to handle this case.
    $endgroup$
    – dreza
    Jan 23 '12 at 22:34










  • $begingroup$
    Hm. Boolean flag would likely be the quickest, but it's not a very object-oriented solution. Let me noodle on that for a few and put something up.
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:37






  • 1




    $begingroup$
    @JesseC.Slicer +1 for the comprehensive example! But I think there's one more thing to add for it to be complete: if the value enclosed in quotes contains a quote itself, then that quote has to be doubled.
    $endgroup$
    – Cristian Lupascu
    Jan 24 '12 at 15:14


















  • $begingroup$
    Excellent. Have edited my post to reflect your suggestions.
    $endgroup$
    – dreza
    Jan 23 '12 at 21:43










  • $begingroup$
    I made an edit to my code. IndexOfAny() should be >= 0 instead of > 0. Whoops!
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:22










  • $begingroup$
    There's a chance that I may not want to include quotations. Would you suggest just adding a boolean flag to the method parameter to handle this case.
    $endgroup$
    – dreza
    Jan 23 '12 at 22:34










  • $begingroup$
    Hm. Boolean flag would likely be the quickest, but it's not a very object-oriented solution. Let me noodle on that for a few and put something up.
    $endgroup$
    – Jesse C. Slicer
    Jan 23 '12 at 22:37






  • 1




    $begingroup$
    @JesseC.Slicer +1 for the comprehensive example! But I think there's one more thing to add for it to be complete: if the value enclosed in quotes contains a quote itself, then that quote has to be doubled.
    $endgroup$
    – Cristian Lupascu
    Jan 24 '12 at 15:14
















$begingroup$
Excellent. Have edited my post to reflect your suggestions.
$endgroup$
– dreza
Jan 23 '12 at 21:43




$begingroup$
Excellent. Have edited my post to reflect your suggestions.
$endgroup$
– dreza
Jan 23 '12 at 21:43












$begingroup$
I made an edit to my code. IndexOfAny() should be >= 0 instead of > 0. Whoops!
$endgroup$
– Jesse C. Slicer
Jan 23 '12 at 22:22




$begingroup$
I made an edit to my code. IndexOfAny() should be >= 0 instead of > 0. Whoops!
$endgroup$
– Jesse C. Slicer
Jan 23 '12 at 22:22












$begingroup$
There's a chance that I may not want to include quotations. Would you suggest just adding a boolean flag to the method parameter to handle this case.
$endgroup$
– dreza
Jan 23 '12 at 22:34




$begingroup$
There's a chance that I may not want to include quotations. Would you suggest just adding a boolean flag to the method parameter to handle this case.
$endgroup$
– dreza
Jan 23 '12 at 22:34












$begingroup$
Hm. Boolean flag would likely be the quickest, but it's not a very object-oriented solution. Let me noodle on that for a few and put something up.
$endgroup$
– Jesse C. Slicer
Jan 23 '12 at 22:37




$begingroup$
Hm. Boolean flag would likely be the quickest, but it's not a very object-oriented solution. Let me noodle on that for a few and put something up.
$endgroup$
– Jesse C. Slicer
Jan 23 '12 at 22:37




1




1




$begingroup$
@JesseC.Slicer +1 for the comprehensive example! But I think there's one more thing to add for it to be complete: if the value enclosed in quotes contains a quote itself, then that quote has to be doubled.
$endgroup$
– Cristian Lupascu
Jan 24 '12 at 15:14




$begingroup$
@JesseC.Slicer +1 for the comprehensive example! But I think there's one more thing to add for it to be complete: if the value enclosed in quotes contains a quote itself, then that quote has to be doubled.
$endgroup$
– Cristian Lupascu
Jan 24 '12 at 15:14













3












$begingroup$

Here is my suggested version, which you should take as coming from a fellow learner.




  1. ToCsv is doing too much. I would be inclined to keep it very simple
    and perform only its very narrow responsibility: properly
    transforming a sequence of strings to a properly formatted csv
    string. Let the caller transform its sequence of arbitrary objects
    into strings.


  2. String.Join exists in the BCL, which is exactly suited to this
    purpose.


  3. Properly encodes the quote character




private class Program
{
private static void Main(string args)
{
int someInts = {1, 2, 3, 4, 10, 9, 8};
string someStrings = {"one", "two", "seven", "eight"};
string specialStrings = {"o'ne", ""tw"o", ",,three", "fo,ur", "five"};

Console.WriteLine(someInts.Select(s => s.ToString()).ToCsv());
Console.WriteLine(someStrings.ToCsv());
Console.WriteLine(specialStrings.ToCsv());
}
}

public static class CsvHelpers
{
private static readonly char csvSpecialChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv(this IEnumerable<string> source)
{
if (source == null)
{
return string.Empty;
}

var encodedStrings = from item in source
select EncodeCsvField(item);

return string.Join(",", encodedStrings);
}

private static string EncodeCsvField(string input)
{
string encodedString = input.Replace(""", """");
if (input.IndexOfAny(csvSpecialChars) >= 0)
{
encodedString = """ + encodedString + """;
}

return encodedString;
}
}





share|improve this answer









$endgroup$













  • $begingroup$
    I sort of wanted to stay away from putting the dependancy on the string object itself in the IEnumerable hence the use of generics. However didn't really know about the Join operater. Thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:13










  • $begingroup$
    My reasoning was that you either provide the function to project from your object to string or just do the projection (possibly through LINQ so you could defer the execution) at the call site. Seemed simpler to just do the projection at the call site if needed so that the extension method has only one responsibility. Cheers.
    $endgroup$
    – WorkerThread
    Jan 24 '12 at 21:13










  • $begingroup$
    Simple, short, clean, correct. Thanks.
    $endgroup$
    – mmdemirbas
    Mar 6 '12 at 11:29
















3












$begingroup$

Here is my suggested version, which you should take as coming from a fellow learner.




  1. ToCsv is doing too much. I would be inclined to keep it very simple
    and perform only its very narrow responsibility: properly
    transforming a sequence of strings to a properly formatted csv
    string. Let the caller transform its sequence of arbitrary objects
    into strings.


  2. String.Join exists in the BCL, which is exactly suited to this
    purpose.


  3. Properly encodes the quote character




private class Program
{
private static void Main(string args)
{
int someInts = {1, 2, 3, 4, 10, 9, 8};
string someStrings = {"one", "two", "seven", "eight"};
string specialStrings = {"o'ne", ""tw"o", ",,three", "fo,ur", "five"};

Console.WriteLine(someInts.Select(s => s.ToString()).ToCsv());
Console.WriteLine(someStrings.ToCsv());
Console.WriteLine(specialStrings.ToCsv());
}
}

public static class CsvHelpers
{
private static readonly char csvSpecialChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv(this IEnumerable<string> source)
{
if (source == null)
{
return string.Empty;
}

var encodedStrings = from item in source
select EncodeCsvField(item);

return string.Join(",", encodedStrings);
}

private static string EncodeCsvField(string input)
{
string encodedString = input.Replace(""", """");
if (input.IndexOfAny(csvSpecialChars) >= 0)
{
encodedString = """ + encodedString + """;
}

return encodedString;
}
}





share|improve this answer









$endgroup$













  • $begingroup$
    I sort of wanted to stay away from putting the dependancy on the string object itself in the IEnumerable hence the use of generics. However didn't really know about the Join operater. Thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:13










  • $begingroup$
    My reasoning was that you either provide the function to project from your object to string or just do the projection (possibly through LINQ so you could defer the execution) at the call site. Seemed simpler to just do the projection at the call site if needed so that the extension method has only one responsibility. Cheers.
    $endgroup$
    – WorkerThread
    Jan 24 '12 at 21:13










  • $begingroup$
    Simple, short, clean, correct. Thanks.
    $endgroup$
    – mmdemirbas
    Mar 6 '12 at 11:29














3












3








3





$begingroup$

Here is my suggested version, which you should take as coming from a fellow learner.




  1. ToCsv is doing too much. I would be inclined to keep it very simple
    and perform only its very narrow responsibility: properly
    transforming a sequence of strings to a properly formatted csv
    string. Let the caller transform its sequence of arbitrary objects
    into strings.


  2. String.Join exists in the BCL, which is exactly suited to this
    purpose.


  3. Properly encodes the quote character




private class Program
{
private static void Main(string args)
{
int someInts = {1, 2, 3, 4, 10, 9, 8};
string someStrings = {"one", "two", "seven", "eight"};
string specialStrings = {"o'ne", ""tw"o", ",,three", "fo,ur", "five"};

Console.WriteLine(someInts.Select(s => s.ToString()).ToCsv());
Console.WriteLine(someStrings.ToCsv());
Console.WriteLine(specialStrings.ToCsv());
}
}

public static class CsvHelpers
{
private static readonly char csvSpecialChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv(this IEnumerable<string> source)
{
if (source == null)
{
return string.Empty;
}

var encodedStrings = from item in source
select EncodeCsvField(item);

return string.Join(",", encodedStrings);
}

private static string EncodeCsvField(string input)
{
string encodedString = input.Replace(""", """");
if (input.IndexOfAny(csvSpecialChars) >= 0)
{
encodedString = """ + encodedString + """;
}

return encodedString;
}
}





share|improve this answer









$endgroup$



Here is my suggested version, which you should take as coming from a fellow learner.




  1. ToCsv is doing too much. I would be inclined to keep it very simple
    and perform only its very narrow responsibility: properly
    transforming a sequence of strings to a properly formatted csv
    string. Let the caller transform its sequence of arbitrary objects
    into strings.


  2. String.Join exists in the BCL, which is exactly suited to this
    purpose.


  3. Properly encodes the quote character




private class Program
{
private static void Main(string args)
{
int someInts = {1, 2, 3, 4, 10, 9, 8};
string someStrings = {"one", "two", "seven", "eight"};
string specialStrings = {"o'ne", ""tw"o", ",,three", "fo,ur", "five"};

Console.WriteLine(someInts.Select(s => s.ToString()).ToCsv());
Console.WriteLine(someStrings.ToCsv());
Console.WriteLine(specialStrings.ToCsv());
}
}

public static class CsvHelpers
{
private static readonly char csvSpecialChars = new { ',', '"', ' ', 'n', 'r' };

public static string ToCsv(this IEnumerable<string> source)
{
if (source == null)
{
return string.Empty;
}

var encodedStrings = from item in source
select EncodeCsvField(item);

return string.Join(",", encodedStrings);
}

private static string EncodeCsvField(string input)
{
string encodedString = input.Replace(""", """");
if (input.IndexOfAny(csvSpecialChars) >= 0)
{
encodedString = """ + encodedString + """;
}

return encodedString;
}
}






share|improve this answer












share|improve this answer



share|improve this answer










answered Jan 24 '12 at 17:22









WorkerThreadWorkerThread

1484




1484












  • $begingroup$
    I sort of wanted to stay away from putting the dependancy on the string object itself in the IEnumerable hence the use of generics. However didn't really know about the Join operater. Thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:13










  • $begingroup$
    My reasoning was that you either provide the function to project from your object to string or just do the projection (possibly through LINQ so you could defer the execution) at the call site. Seemed simpler to just do the projection at the call site if needed so that the extension method has only one responsibility. Cheers.
    $endgroup$
    – WorkerThread
    Jan 24 '12 at 21:13










  • $begingroup$
    Simple, short, clean, correct. Thanks.
    $endgroup$
    – mmdemirbas
    Mar 6 '12 at 11:29


















  • $begingroup$
    I sort of wanted to stay away from putting the dependancy on the string object itself in the IEnumerable hence the use of generics. However didn't really know about the Join operater. Thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:13










  • $begingroup$
    My reasoning was that you either provide the function to project from your object to string or just do the projection (possibly through LINQ so you could defer the execution) at the call site. Seemed simpler to just do the projection at the call site if needed so that the extension method has only one responsibility. Cheers.
    $endgroup$
    – WorkerThread
    Jan 24 '12 at 21:13










  • $begingroup$
    Simple, short, clean, correct. Thanks.
    $endgroup$
    – mmdemirbas
    Mar 6 '12 at 11:29
















$begingroup$
I sort of wanted to stay away from putting the dependancy on the string object itself in the IEnumerable hence the use of generics. However didn't really know about the Join operater. Thanks.
$endgroup$
– dreza
Jan 24 '12 at 20:13




$begingroup$
I sort of wanted to stay away from putting the dependancy on the string object itself in the IEnumerable hence the use of generics. However didn't really know about the Join operater. Thanks.
$endgroup$
– dreza
Jan 24 '12 at 20:13












$begingroup$
My reasoning was that you either provide the function to project from your object to string or just do the projection (possibly through LINQ so you could defer the execution) at the call site. Seemed simpler to just do the projection at the call site if needed so that the extension method has only one responsibility. Cheers.
$endgroup$
– WorkerThread
Jan 24 '12 at 21:13




$begingroup$
My reasoning was that you either provide the function to project from your object to string or just do the projection (possibly through LINQ so you could defer the execution) at the call site. Seemed simpler to just do the projection at the call site if needed so that the extension method has only one responsibility. Cheers.
$endgroup$
– WorkerThread
Jan 24 '12 at 21:13












$begingroup$
Simple, short, clean, correct. Thanks.
$endgroup$
– mmdemirbas
Mar 6 '12 at 11:29




$begingroup$
Simple, short, clean, correct. Thanks.
$endgroup$
– mmdemirbas
Mar 6 '12 at 11:29











2












$begingroup$

A couple of things:




  1. Although you are using a StringBuilder (which is good in this case), you're generating a string for each row of data. If you're intending to use this to create multiple rows of data, then it will not quite as efficient as it could be. Maybe this is no big deal, although that might depend on how much data you're processing.


  2. Although I don't feel it's a major concern, I don't like stripping off the trailing comma. I always like to avoid messing up my output in the first place, rather than having additional code at the end that fixes the problem. One thing to realize, though, is that your StringBuilder generates an entire string in memory to contain the row of data, but when you call TrimEnd, I believe that this will create yet another string containing the entire row of data. Not very efficient.



I imagine that in most cases, this might be used to output to a file, or perhaps some other kind of stream. Therefore, it would make more sense to me to rewrite this functionality to write to a stream.



I've provided an alternate implementation below. The core functionality is in the WriteAsCsv method for the TextWriter. There are a few overloads to make this functionality easy to use for someone who has access to a FileStream object, a StringBuilder object, or just the original IEnumerable object.



This approach uses an enumerator in order to avoid the issue with the trailing comma.



    static void WriteCsvValue(System.IO.TextWriter writer, string str)
{
if (str.IndexOfAny(csvChars) > 0)
{
writer.Write(""");
writer.Write(str); // TODO: perform any necessary escaping
writer.Write(""");
}
else
{
writer.Write(str);
}
}

public static void WriteAsCsv<T>(this System.IO.TextWriter writer, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

var items = from item in source.Select(getItem)
where item != null
select item.ToString();

using (var enumerator = items.GetEnumerator())
{
if (enumerator.MoveNext())
{
WriteCsvValue(writer, enumerator.Current);

while (enumerator.MoveNext())
{
writer.Write(", ");
WriteCsvValue(writer, enumerator.Current);
}
}
}
}

public static void WriteAsCsv<T>(this System.IO.Stream stream, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StreamWriter(stream))
{
writer.WriteAsCsv(source, getItem);
}
}

public static void AppendAsCsv<T>(this StringBuilder builder, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StringWriter(builder))
{
writer.WriteAsCsv(source, getItem);
}
}

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
StringBuilder builder = new StringBuilder();
builder.AppendAsCsv(source, getItem);
return builder.ToString();
}



  • A StreamWriter is a TextWriter that wraps a Stream object.

  • A StringWriter is a TextWriter that wraps a StringBuilder object.


The gist of what I've done here is to decompose your original approach into separate pieces, making it easier to consume in multiple different ways.






share|improve this answer









$endgroup$













  • $begingroup$
    Although efficiency in the particular case I'm using it for isn't an issue as I'm not dealing with 100's of lines etc it's still something I didn't take into account. thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:15










  • $begingroup$
    @dreza if you don't care to use an enumerator (I know it feels clunky, at least until you get used to it), you could avoid having to strip off the trailing comma by prepending the comma: if(builder.Length>0)builder.Append(", ");
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:20










  • $begingroup$
    @dreza - See my answer on StackOverflow to a similar question.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:54
















2












$begingroup$

A couple of things:




  1. Although you are using a StringBuilder (which is good in this case), you're generating a string for each row of data. If you're intending to use this to create multiple rows of data, then it will not quite as efficient as it could be. Maybe this is no big deal, although that might depend on how much data you're processing.


  2. Although I don't feel it's a major concern, I don't like stripping off the trailing comma. I always like to avoid messing up my output in the first place, rather than having additional code at the end that fixes the problem. One thing to realize, though, is that your StringBuilder generates an entire string in memory to contain the row of data, but when you call TrimEnd, I believe that this will create yet another string containing the entire row of data. Not very efficient.



I imagine that in most cases, this might be used to output to a file, or perhaps some other kind of stream. Therefore, it would make more sense to me to rewrite this functionality to write to a stream.



I've provided an alternate implementation below. The core functionality is in the WriteAsCsv method for the TextWriter. There are a few overloads to make this functionality easy to use for someone who has access to a FileStream object, a StringBuilder object, or just the original IEnumerable object.



This approach uses an enumerator in order to avoid the issue with the trailing comma.



    static void WriteCsvValue(System.IO.TextWriter writer, string str)
{
if (str.IndexOfAny(csvChars) > 0)
{
writer.Write(""");
writer.Write(str); // TODO: perform any necessary escaping
writer.Write(""");
}
else
{
writer.Write(str);
}
}

public static void WriteAsCsv<T>(this System.IO.TextWriter writer, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

var items = from item in source.Select(getItem)
where item != null
select item.ToString();

using (var enumerator = items.GetEnumerator())
{
if (enumerator.MoveNext())
{
WriteCsvValue(writer, enumerator.Current);

while (enumerator.MoveNext())
{
writer.Write(", ");
WriteCsvValue(writer, enumerator.Current);
}
}
}
}

public static void WriteAsCsv<T>(this System.IO.Stream stream, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StreamWriter(stream))
{
writer.WriteAsCsv(source, getItem);
}
}

public static void AppendAsCsv<T>(this StringBuilder builder, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StringWriter(builder))
{
writer.WriteAsCsv(source, getItem);
}
}

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
StringBuilder builder = new StringBuilder();
builder.AppendAsCsv(source, getItem);
return builder.ToString();
}



  • A StreamWriter is a TextWriter that wraps a Stream object.

  • A StringWriter is a TextWriter that wraps a StringBuilder object.


The gist of what I've done here is to decompose your original approach into separate pieces, making it easier to consume in multiple different ways.






share|improve this answer









$endgroup$













  • $begingroup$
    Although efficiency in the particular case I'm using it for isn't an issue as I'm not dealing with 100's of lines etc it's still something I didn't take into account. thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:15










  • $begingroup$
    @dreza if you don't care to use an enumerator (I know it feels clunky, at least until you get used to it), you could avoid having to strip off the trailing comma by prepending the comma: if(builder.Length>0)builder.Append(", ");
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:20










  • $begingroup$
    @dreza - See my answer on StackOverflow to a similar question.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:54














2












2








2





$begingroup$

A couple of things:




  1. Although you are using a StringBuilder (which is good in this case), you're generating a string for each row of data. If you're intending to use this to create multiple rows of data, then it will not quite as efficient as it could be. Maybe this is no big deal, although that might depend on how much data you're processing.


  2. Although I don't feel it's a major concern, I don't like stripping off the trailing comma. I always like to avoid messing up my output in the first place, rather than having additional code at the end that fixes the problem. One thing to realize, though, is that your StringBuilder generates an entire string in memory to contain the row of data, but when you call TrimEnd, I believe that this will create yet another string containing the entire row of data. Not very efficient.



I imagine that in most cases, this might be used to output to a file, or perhaps some other kind of stream. Therefore, it would make more sense to me to rewrite this functionality to write to a stream.



I've provided an alternate implementation below. The core functionality is in the WriteAsCsv method for the TextWriter. There are a few overloads to make this functionality easy to use for someone who has access to a FileStream object, a StringBuilder object, or just the original IEnumerable object.



This approach uses an enumerator in order to avoid the issue with the trailing comma.



    static void WriteCsvValue(System.IO.TextWriter writer, string str)
{
if (str.IndexOfAny(csvChars) > 0)
{
writer.Write(""");
writer.Write(str); // TODO: perform any necessary escaping
writer.Write(""");
}
else
{
writer.Write(str);
}
}

public static void WriteAsCsv<T>(this System.IO.TextWriter writer, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

var items = from item in source.Select(getItem)
where item != null
select item.ToString();

using (var enumerator = items.GetEnumerator())
{
if (enumerator.MoveNext())
{
WriteCsvValue(writer, enumerator.Current);

while (enumerator.MoveNext())
{
writer.Write(", ");
WriteCsvValue(writer, enumerator.Current);
}
}
}
}

public static void WriteAsCsv<T>(this System.IO.Stream stream, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StreamWriter(stream))
{
writer.WriteAsCsv(source, getItem);
}
}

public static void AppendAsCsv<T>(this StringBuilder builder, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StringWriter(builder))
{
writer.WriteAsCsv(source, getItem);
}
}

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
StringBuilder builder = new StringBuilder();
builder.AppendAsCsv(source, getItem);
return builder.ToString();
}



  • A StreamWriter is a TextWriter that wraps a Stream object.

  • A StringWriter is a TextWriter that wraps a StringBuilder object.


The gist of what I've done here is to decompose your original approach into separate pieces, making it easier to consume in multiple different ways.






share|improve this answer









$endgroup$



A couple of things:




  1. Although you are using a StringBuilder (which is good in this case), you're generating a string for each row of data. If you're intending to use this to create multiple rows of data, then it will not quite as efficient as it could be. Maybe this is no big deal, although that might depend on how much data you're processing.


  2. Although I don't feel it's a major concern, I don't like stripping off the trailing comma. I always like to avoid messing up my output in the first place, rather than having additional code at the end that fixes the problem. One thing to realize, though, is that your StringBuilder generates an entire string in memory to contain the row of data, but when you call TrimEnd, I believe that this will create yet another string containing the entire row of data. Not very efficient.



I imagine that in most cases, this might be used to output to a file, or perhaps some other kind of stream. Therefore, it would make more sense to me to rewrite this functionality to write to a stream.



I've provided an alternate implementation below. The core functionality is in the WriteAsCsv method for the TextWriter. There are a few overloads to make this functionality easy to use for someone who has access to a FileStream object, a StringBuilder object, or just the original IEnumerable object.



This approach uses an enumerator in order to avoid the issue with the trailing comma.



    static void WriteCsvValue(System.IO.TextWriter writer, string str)
{
if (str.IndexOfAny(csvChars) > 0)
{
writer.Write(""");
writer.Write(str); // TODO: perform any necessary escaping
writer.Write(""");
}
else
{
writer.Write(str);
}
}

public static void WriteAsCsv<T>(this System.IO.TextWriter writer, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

var items = from item in source.Select(getItem)
where item != null
select item.ToString();

using (var enumerator = items.GetEnumerator())
{
if (enumerator.MoveNext())
{
WriteCsvValue(writer, enumerator.Current);

while (enumerator.MoveNext())
{
writer.Write(", ");
WriteCsvValue(writer, enumerator.Current);
}
}
}
}

public static void WriteAsCsv<T>(this System.IO.Stream stream, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StreamWriter(stream))
{
writer.WriteAsCsv(source, getItem);
}
}

public static void AppendAsCsv<T>(this StringBuilder builder, IEnumerable<T> source, Func<T, object> getItem)
{
if ((source == null) || (getItem == null))
{
return;
}

using (var writer = new System.IO.StringWriter(builder))
{
writer.WriteAsCsv(source, getItem);
}
}

public static string ToCsv<T>(this IEnumerable<T> source, Func<T, object> getItem)
{
StringBuilder builder = new StringBuilder();
builder.AppendAsCsv(source, getItem);
return builder.ToString();
}



  • A StreamWriter is a TextWriter that wraps a Stream object.

  • A StringWriter is a TextWriter that wraps a StringBuilder object.


The gist of what I've done here is to decompose your original approach into separate pieces, making it easier to consume in multiple different ways.







share|improve this answer












share|improve this answer



share|improve this answer










answered Jan 24 '12 at 17:43









Dr. Wily's ApprenticeDr. Wily's Apprentice

88957




88957












  • $begingroup$
    Although efficiency in the particular case I'm using it for isn't an issue as I'm not dealing with 100's of lines etc it's still something I didn't take into account. thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:15










  • $begingroup$
    @dreza if you don't care to use an enumerator (I know it feels clunky, at least until you get used to it), you could avoid having to strip off the trailing comma by prepending the comma: if(builder.Length>0)builder.Append(", ");
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:20










  • $begingroup$
    @dreza - See my answer on StackOverflow to a similar question.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:54


















  • $begingroup$
    Although efficiency in the particular case I'm using it for isn't an issue as I'm not dealing with 100's of lines etc it's still something I didn't take into account. thanks.
    $endgroup$
    – dreza
    Jan 24 '12 at 20:15










  • $begingroup$
    @dreza if you don't care to use an enumerator (I know it feels clunky, at least until you get used to it), you could avoid having to strip off the trailing comma by prepending the comma: if(builder.Length>0)builder.Append(", ");
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:20










  • $begingroup$
    @dreza - See my answer on StackOverflow to a similar question.
    $endgroup$
    – Dr. Wily's Apprentice
    Jan 24 '12 at 20:54
















$begingroup$
Although efficiency in the particular case I'm using it for isn't an issue as I'm not dealing with 100's of lines etc it's still something I didn't take into account. thanks.
$endgroup$
– dreza
Jan 24 '12 at 20:15




$begingroup$
Although efficiency in the particular case I'm using it for isn't an issue as I'm not dealing with 100's of lines etc it's still something I didn't take into account. thanks.
$endgroup$
– dreza
Jan 24 '12 at 20:15












$begingroup$
@dreza if you don't care to use an enumerator (I know it feels clunky, at least until you get used to it), you could avoid having to strip off the trailing comma by prepending the comma: if(builder.Length>0)builder.Append(", ");
$endgroup$
– Dr. Wily's Apprentice
Jan 24 '12 at 20:20




$begingroup$
@dreza if you don't care to use an enumerator (I know it feels clunky, at least until you get used to it), you could avoid having to strip off the trailing comma by prepending the comma: if(builder.Length>0)builder.Append(", ");
$endgroup$
– Dr. Wily's Apprentice
Jan 24 '12 at 20:20












$begingroup$
@dreza - See my answer on StackOverflow to a similar question.
$endgroup$
– Dr. Wily's Apprentice
Jan 24 '12 at 20:54




$begingroup$
@dreza - See my answer on StackOverflow to a similar question.
$endgroup$
– Dr. Wily's Apprentice
Jan 24 '12 at 20:54


















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f8228%2fconverting-a-list-to-a-csv-string%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

How to reconfigure Docker Trusted Registry 2.x.x to use CEPH FS mount instead of NFS and other traditional...

is 'sed' thread safe

How to make a Squid Proxy server?