Converting a list to a CSV string 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&lt;T&gt;(this IEnumerable&lt;T&gt; source, Func&lt;T, object&gt; 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) &gt; 0)
        {
            builder.Append("\"").Append(str).Append("\"").Append(", ");
        }
        else
        {
            builder.Append(str).Append(", ");
        }
    }

    var csv = builder.ToString();

    return csv.Length &gt; 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.
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&lt;T&gt;
    {
        private readonly IEnumerable&lt;T&gt; values;

        private readonly Func&lt;T, object&gt; getItem;

        protected CsvBase(IEnumerable&lt;T&gt; values, Func&lt;T, object&gt; 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&lt;T&gt; : CsvBase&lt;T&gt;
    {
        public CsvBare(IEnumerable&lt;T&gt; values, Func&lt;T, object&gt; getItem) : base(values, getItem)
        {
        }

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

    public sealed class CsvTrimBare&lt;T&gt; : CsvBare&lt;T&gt;
    {
        public CsvTrimBare(IEnumerable&lt;T&gt; values, Func&lt;T, object&gt; getItem) : base(values, getItem)
        {
        }

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

    public class CsvRfc4180&lt;T&gt; : CsvBase&lt;T&gt;
    {
        public CsvRfc4180(IEnumerable&lt;T&gt; values, Func&lt;T, object&gt; getItem) : base(values, getItem)
        {
        }

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

    public sealed class CsvTrimRfc4180&lt;T&gt; : CsvRfc4180&lt;T&gt;
    {
        public CsvTrimRfc4180(IEnumerable&lt;T&gt; values, Func&lt;T, object&gt; getItem) : base(values, getItem)
        {
        }

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

    public class CsvAlwaysQuote&lt;T&gt; : CsvBare&lt;T&gt;
    {
        public CsvAlwaysQuote(IEnumerable&lt;T&gt; values, Func&lt;T, object&gt; getItem) : base(values, getItem)
        {
        }

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

    public sealed class CsvTrimAlwaysQuote&lt;T&gt; : CsvAlwaysQuote&lt;T&gt;
    {
        public CsvTrimAlwaysQuote(IEnumerable&lt;T&gt; values, Func&lt;T, object&gt; 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&lt;T&gt;(this IEnumerable&lt;T&gt; source, Func&lt;T, object&gt; getItem, Type csvProcessorType)
        {
            if ((source == null)
                || (getItem == null)
                || (csvProcessorType == null)
                || !csvProcessorType.IsSubclassOf(typeof(CsvBase&lt;T&gt;)))
            {
                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 =&gt; word, typeof(CsvAlwaysQuote&lt;string&gt;)));
            Console.WriteLine(words.ToCsv(word =&gt; word, typeof(CsvRfc4180&lt;string&gt;)));
            Console.WriteLine(words.ToCsv(word =&gt; word, typeof(CsvBare&lt;string&gt;)));
            Console.WriteLine(words.ToCsv(word =&gt; word, typeof(CsvTrimAlwaysQuote&lt;string&gt;)));
            Console.WriteLine(words.ToCsv(word =&gt; word, typeof(CsvTrimRfc4180&lt;string&gt;)));
            Console.WriteLine(words.ToCsv(word =&gt; word, typeof(CsvTrimBare&lt;string&gt;)));
            Console.ReadLine();
        }
    }
}