Cómo convertir una lista en una cadena delimitada en C#

En este artículo te mostraremos cómo, con un sencillo método en C#, es posible convertir una lista en una cadena delimitada por cualquier carácter. A priori te puede parece un poco inútil esta acción pero, ¿y si te digo que lo podemos utilizar para crear archivos CSV? Imaginemos que tenemos una lista con los datos por ejemplo de los alumnos de un colegio y queremos exportarla a un archivo CSV para importarlo en otro software distinto. Pues con este método es posible. Pasaremos de tener una lista de datos a una cadena de texto delimitada por el caracter que nosotros creamos conveniente. Y todo de una manera sencilla y sobretodo rápida.

La verdad que hoy he tenido que llevarlo a la práctica. Me han pedido una aplicación que exporte los datos de una lista a un archivo CSV y me he peleado con este método. Al acabar, he pensado... ¿por qué no compartirlo con toda la comunidad de programacion.net? Y eso es lo que he hecho. A continuación os explico cómo convertir una lista en una cadena delimitada en C# con unos sencillos ejemplos que creo que comprenderéis todos, o casi todos. ¿Estás preparado? ¡Pues vamos allá!

Cómo convertir una lista en una cadena delimitada

Para llamar a la extensión debes hacerlo de esta manera:

myList.ToDelimitedText(delimiter, trimTrailingNewLineIfExists)

Como podéis observar, pasamos como parámetro al método en primer lugar el caracter con el que queremos delimitar la lista. Este método nos devolverá una cadena de texto delimitada que podremos usar para otros menesteres.

He hecho una compilación de las distintas pruebas para mostraros el método en funcionamiento:

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
            {
                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
            {
                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
            {
                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
            {
                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
    }
}

Y el código real del método que he tenido que utilizar hoy ha sido este:

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

namespace Gists.Extensions.ListOfTExtentions
{
    public static class ListOfTExtentions
    {
        public static string ToDelimitedText(this List instance, 
            string delimiter, 
            bool trimTrailingNewLineIfExists = false)
            where T : class, new()
        {
            int itemCount = instance.Count;
            if (itemCount == 0) return string.Empty;

            var properties = GetPropertiesOfType();
            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(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() 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;
        }
    }
}

Cómo véis un métod sencillo, fácil de realizar y de poner en práctica. Esperamos que nos des tu opinión acerca de este método y nos digas si te ha funcionado tan bien como me ha funcionado a mi. En programación.net seguiremos publicando aquellos códigos que nos han resultado útiles a nosotros mismos para compartirlos con todos vosotros. Y tú, ¿no te animas a compartir tus códigos con nosotros?

Fuente: dibley1973

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP