Generic CSV Writer

What follows is a generic class for generating CSV files.

Usage

    CsvWriter<MyObject> writer = new CsvWriter<MyObject>();
    writer.Header = true;
    writer.AddColumn("Id", x => x.Id.ToString());
    writer.AddColumn("Name", x => x.Name);
    writer.AddColumn("Price", x => x.Price.ToString());
    writer.AddColumn("Guid", x => x.Guid);
    writer.AddColumn("Description", x => x.Description);
    writer.AddColumn("Url", x => x.Url);

    IEnumerable<MyObject> objects = Load();
    string csv = writer.WriteToString(objects);
    File.WriteAllText("objects.csv", csv);

Classes

    class CsvWriter<T>
    {
        private readonly List<CsvColumn<T>> _columns = new List<CsvColumn<T>>();

        public bool Header { get; set; }
        public bool QuoteAll { get; set; }
        public IEnumerable<CsvColumn<T>> Columns { get { return _columns; } }

        public void AddColumn(string name, Func<T, string> value)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (value == null) throw new ArgumentNullException("value");

            foreach (CsvColumn<T> column in _columns)
                if (column.Name == name)
                    throw new Exception("Column with same name already added.");

            _columns.Add(new CsvColumn<T>(name, value));
        }

        public string WriteToString(IEnumerable<T> items)
        {
            if (items == null) throw new ArgumentNullException("items");

            StringWriter writer = new StringWriter();
            WriteToStream(writer, items);
            return writer.ToString();
        }

        public void WriteToStream(TextWriter stream, IEnumerable<T> items)
        {
            if (stream == null) throw new ArgumentNullException("stream");
            if (items == null) throw new ArgumentNullException("items");

            if (Header)
                WriteHeader(stream);

            foreach (T item in items)
                WriteRow(stream, item);
        }

        private void WriteHeader(TextWriter stream)
        {
            bool first = true;
            foreach (CsvColumn<T> column in _columns)
            {
                if (first)
                    first = false;
                else
                    stream.Write(',');

                WriteValue(stream, column.Name);
            }
            stream.Write('\n');
        }

        private void WriteRow(TextWriter stream, T item)
        {
            if (item == null) return;

            bool first = true;
            foreach (CsvColumn<T> column in _columns)
            {
                if (first)
                    first = false;
                else
                    stream.Write(',');

                WriteValue(stream, column.Value(item));
            }
            stream.Write('\n');
        }

        private void WriteValue(TextWriter stream, string value)
        {
            if (value == null) return;

            if (QuoteAll || ContainsAny(value, '\"', ',', '\x0A', '\x0D'))
            {
                stream.Write("\"");
                stream.Write(value.Replace("\"", "\"\""));
                stream.Write("\"");
            }
            else
            {
                stream.Write(value);
            }
        }

        private static bool ContainsAny(string value, params char[] characters)
        {
            return value.IndexOfAny(characters) != -1;
        }
    }

    class CsvColumn<T>
    {
        public string Name { get; private set; }
        public Func<T, string> Value { get; private set; }

        public CsvColumn(string name, Func<T, string> value)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (value == null) throw new ArgumentNullException("value");

            Name = name;
            Value = value;
        }
    }