Click here to Skip to main content
15,881,413 members
Articles / Programming Languages / C#

C# Extension Method to Convert List into a Delimited Test String

Rate me:
Please Sign up or sign in to vote.
3.93/5 (4 votes)
19 Oct 2015CPOL 12.5K   11   8
Have you ever needed to convert a list of objects into a CSV file? If you need to in the future, this short extension of the Listof T will help you.

Have you ever needed to convert a list of objects into a CSV file? If you need to in the future, this short extension of the List of T will help you.

C#
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Gists.Extensions.ListOfTExtentions
{
    public static class ListOfTExtentions
    {
        /// <summary>
        /// Converts this instance to delimited text.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="instance">The instance.</param>
        /// <param name="delimiter">The delimiter.</param>
        /// <param name="trimTrailingNewLineIfExists">
        /// If set to <c>true</c> then trim trailing new line if it exists.
        /// </param>
        /// <returns></returns>
        public static string ToDelimitedText<T>(this List<T> instance, 
            string delimiter, 
            bool trimTrailingNewLineIfExists = false)
            where T : class, new()
        {
            int itemCount = instance.Count;
            if (itemCount == 0) return string.Empty;

            var properties = GetPropertiesOfType<T>();
            int propertyCount = properties.Length;
            var outputBuilder = new StringBuilder();

            for (int itemIndex = 0; itemIndex < itemCount; itemIndex++)
            {
                T listItem = instance[itemIndex];
                AppendListItemToOutputBuilder
                (outputBuilder, listItem, properties, propertyCount, delimiter);

                AddNewLineIfRequired(trimTrailingNewLineIfExists, itemIndex, itemCount, outputBuilder);
            }

            var output = TrimTrailingNewLineIfExistsAndRequired
            (outputBuilder.ToString(), trimTrailingNewLineIfExists);
            return output;
        }

        private static void AddDelimiterIfRequired
        (StringBuilder outputBuilder, int propertyCount, string delimiter,
            int propertyIndex)
        {
            bool isLastProperty = (propertyIndex + 1 == propertyCount);
            if (!isLastProperty)
            {
                outputBuilder.Append(delimiter);
            }
        }

        private static void AddNewLineIfRequired
        (bool trimTrailingNewLineIfExists, int itemIndex, int itemCount,
            StringBuilder outputBuilder)
        {
            bool isLastItem = (itemIndex + 1 == itemCount);
            if (!isLastItem || !trimTrailingNewLineIfExists)
            {
                outputBuilder.Append(Environment.NewLine);
            }
        }

        private static void AppendListItemToOutputBuilder<T>(StringBuilder outputBuilder, 
            T listItem, 
            PropertyInfo[] properties,
            int propertyCount,
            string delimiter)
            where T : class, new()
        {
            
            for (int propertyIndex = 0; propertyIndex < properties.Length; propertyIndex += 1)
            {
                var property = properties[propertyIndex];
                var propertyValue = property.GetValue(listItem);
                outputBuilder.Append(propertyValue);

                AddDelimiterIfRequired(outputBuilder, propertyCount, delimiter, propertyIndex);
            }
        }

        private static PropertyInfo[] GetPropertiesOfType<T>() where T : class, new()
        {
            Type itemType = typeof (T);
            var properties = itemType.GetProperties
            (BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.Public);
            return properties;
        }

        private static string TrimTrailingNewLineIfExistsAndRequired
        (string output, bool trimTrailingNewLineIfExists)
        {
            if (!trimTrailingNewLineIfExists || !output.EndsWith(Environment.NewLine)) return output;

            int outputLength = output.Length;
            int newLineLength = Environment.NewLine.Length;
            int startIndex = outputLength - newLineLength;
            output = output.Substring(startIndex, newLineLength);
            return output;
        }
    }
}

A small suite of tests showing usage are given below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Gists.Extensions.ListOfTExtentions;

namespace Gists_Tests.ExtensionTests.ListOfTExtentionTests
{
    [TestClass]
    public class ListOfT_ToDelimitedTextTests
    {
        #region Mock Data

        private class SimpleObject
        {
            public int Id { get; set; }
        }

        private class ComplextObject : SimpleObject
        {
            public string Name { get; set; }
            public bool Active { get; set; }
        }

        #endregion

        #region Tests

        [TestMethod]
        public void ToDelimitedText_ReturnsCorrectNumberOfRows()
        {
            // ARRANGE
            var itemList = new List<ComplextObject>
            {
                new ComplextObject {Id = 1, Name = "Sid", Active = true},
                new ComplextObject {Id = 2, Name = "James", Active = false},
                new ComplextObject {Id = 3, Name = "Ted", Active = true},
            };
            const string delimiter = ",";
            const int expectedRowCount = 3;
            const bool trimTrailingNewLineIfExists = true;

            // ACT
            string result = itemList.ToDelimitedText(delimiter, trimTrailingNewLineIfExists);
            var lines = result.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
            var actualRowCount = lines.Length;

            // ASSERT
            Assert.AreEqual(expectedRowCount, actualRowCount);
        }

        [TestMethod]
        public void ToDelimitedText_ReturnsCorrectNumberOfProperties()
        {
            // ARRANGE
            var itemList = new List<ComplextObject>
            {
                new ComplextObject {Id = 1, Name = "Sid", Active = true}
            };
            const string delimiter = ",";
            const int expectedPropertyCount = 3;

            // ACT
            string result = itemList.ToDelimitedText(delimiter);
            var lines = result.Split(Environment.NewLine.ToCharArray());
            var properties = lines.First().Split(delimiter.ToCharArray());
            var actualPropertyCount = properties.Length;

            // ASSERT
            Assert.AreEqual(expectedPropertyCount, actualPropertyCount);
        }

        [TestMethod]
        public void ToDelimitedText_RemovesTrailingNewLine_WhenSet()
        {
            // ARRANGE
            var itemList = new List<ComplextObject>
            {
                new ComplextObject {Id = 1, Name = "Sid", Active = true},
                new ComplextObject {Id = 2, Name = "James", Active = false},
                new ComplextObject {Id = 3, Name = "Ted", Active = true},
            };
            const string delimiter = ",";
            const bool trimTrailingNewLineIfExists = true;

            // ACT
            string result = itemList.ToDelimitedText(delimiter, trimTrailingNewLineIfExists);
            bool endsWithNewLine = result.EndsWith(Environment.NewLine);

            // ASSERT
            Assert.IsFalse(endsWithNewLine);
        }

        [TestMethod]
        public void ToDelimitedText_IncludesTrailingNewLine_WhenNotSet()
        {
            // ARRANGE
            var itemList = new List<ComplextObject>
            {
                new ComplextObject {Id = 1, Name = "Sid", Active = true},
                new ComplextObject {Id = 2, Name = "James", Active = false},
                new ComplextObject {Id = 3, Name = "Ted", Active = true},
            };
            const string delimiter = ",";
            const bool trimTrailingNewLineIfExists = false;

            // ACT
            string result = itemList.ToDelimitedText(delimiter, trimTrailingNewLineIfExists);
            bool endsWithNewLine = result.EndsWith(Environment.NewLine);

            // ASSERT
            Assert.IsTrue(endsWithNewLine);
        }

        #endregion
    }
}

This code can be found on one of my Gists here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
Duane has worked in a commercial software development environment for 9 years, with all but three for a global fashion retailer.

He is proficient in ASP.Net, MVC, C#, HTML, CSS, JavaScript, SQL Server TSQL.

Comments and Discussions

 
QuestionForeach would clean it up. Pin
Jimmy_James_J22-Oct-15 7:31
Jimmy_James_J22-Oct-15 7:31 
AnswerRe: Foreach would clean it up. Pin
dibley197322-Oct-15 8:49
dibley197322-Oct-15 8:49 
GeneralRe: Foreach would clean it up. Pin
Jimmy_James_J22-Oct-15 9:45
Jimmy_James_J22-Oct-15 9:45 
GeneralRe: Foreach would clean it up. Pin
dibley197322-Oct-15 10:15
dibley197322-Oct-15 10:15 
Good call, putting the newline at the start. I had not thought of it that way!

Nice solution, better than mine and one I can still read!
QuestionNice but 'KISS' Pin
GerVenson20-Oct-15 2:25
professionalGerVenson20-Oct-15 2:25 
AnswerRe: Nice but 'KISS' Pin
dibley197320-Oct-15 10:19
dibley197320-Oct-15 10:19 
GeneralRe: Nice but 'KISS' Pin
GerVenson20-Oct-15 22:07
professionalGerVenson20-Oct-15 22:07 
GeneralRe: Nice but 'KISS' Pin
dibley197321-Oct-15 2:41
dibley197321-Oct-15 2:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.