c# – Creating a CSV stream from an object


Let’s do some performance tweaks.

I will measure the time simply with Stopwatch and first of all I’ll cleanup your code to generate proper CSV (without reduntant empty lines as initial).

List<string> headers = typeof(Lead).GetProperties().Select(x => x.Name).ToList();
string s = string.Join(",", headers) + Environment.NewLine;

foreach (Lead lead in leads)
{
    List<object> values = typeof(Lead).GetProperties().Select(prop => prop.GetValue(lead)).ToList();
    string ss = string.Join(",", values);
    s += $"{ss}{Environment.NewLine}";
}

Environment.NewLine returns rn on Windows and n on Linux.

Output will be

FirstName,MiddleName,LastName
Joe,M,DiFabio
Dave,,Palacios
Andy,A,Nevers

Looks like CSV 🙂

Then to measure performance I’ll imagine that you have very large amount of data e.g. 30000 lines. I’ll run the test in a loop of 10000 iterations.

TestOriginal

private static string TestOriginal(List<Lead> leads)
{
    List<string> headers = typeof(Lead).GetProperties().Select(x => x.Name).ToList();
    string s = string.Join(",", headers) + Environment.NewLine;

    for (int i = 0; i < 10000; i++)
        foreach (Lead lead in leads)
        {
            List<object> values = typeof(Lead).GetProperties().Select(prop => prop.GetValue(lead)).ToList();
            string ss = string.Join(",", values);
            s += $"{ss}{Environment.NewLine}";
        }
    return s;
}

Let’s run.

Stopwatch sw = new Stopwatch();
sw.Start();
string s = TestOriginal(leads);
sw.Stop();
Console.WriteLine("Original: {0}ms", sw.ElapsedMilliseconds);
Original: 3162ms

Ugh. 🙂

Almost everyone knows that Reflection is slow. Let’s somewhat optimize that.

ReflectionTweak

private static string ReflectionTweak(List<Lead> leads)
{
    PropertyInfo() properties = typeof(Lead).GetProperties();
    List<string> headers = properties.Select(x => x.Name).ToList();
    string s = string.Join(",", headers) + Environment.NewLine;

    for (int i = 0; i < 10000; i++)
        foreach (Lead lead in leads)
        {
            List<object> values = properties.Select(prop => prop.GetValue(lead)).ToList();
            string ss = string.Join(",", values);
            s += $"{ss}{Environment.NewLine}";
        }
    return s;
}

Go go go

sw.Restart();
string sr = ReflectionTweak(leads);
sw.Stop();
Console.WriteLine("Reflection tweak: {0}ms, match: {1}", sw.ElapsedMilliseconds, s == sr);

Output with matching the string to original one

Reflection tweak: 3040ms, match: True

Better but still slow

One more idea – StringBuilder. Afaik, it’s the fastest way to build a string (maybe except some unsafe/unmanaged code).

StringBuilderTweak

private static string StringBuilderTweak(List<Lead> leads)
{
    StringBuilder sb = new StringBuilder();
    PropertyInfo() properties = typeof(Lead).GetProperties();
    sb.AppendLine(string.Join(",", properties.Select(x => x.Name)));

    for (int i = 0; i < 10000; i++)
        foreach (Lead lead in leads)
        {
            sb.AppendLine(string.Join(",", properties.Select(prop => prop.GetValue(lead))));
        }
    return sb.ToString();
}

Interesting…

sw.Restart();
string sb = StringBuilderTweak(leads);
sw.Stop();
Console.WriteLine("StringBuilder tweak: {0}ms, match: {1}", sw.ElapsedMilliseconds, s == sb);

Output

StringBuilder tweak: 24ms, match: True

WOW!!!


Result

StringBuilder sb = new StringBuilder();
PropertyInfo() properties = typeof(Lead).GetProperties();
sb.AppendLine(string.Join(",", properties.Select(x => x.Name)));

foreach (Lead lead in leads)
{
    sb.AppendLine(string.Join(",", properties.Select(prop => prop.GetValue(lead))));
}
string s = sb.ToString();

The whole code to reproduce and play more

internal class Program
{
    public static void Main(string() args)
    {
        List<Lead> leads = new List<Lead>();
        Lead lead1 = new Lead();
        lead1.FirstName = "Joe";
        lead1.MiddleName = "M";
        lead1.LastName = "DiFabio";
        leads.Add(lead1);

        Lead lead2 = new Lead();
        lead2.FirstName = "Dave";
        lead2.MiddleName = "";
        lead2.LastName = "Palacios";
        leads.Add(lead2);

        Lead lead3 = new Lead();
        lead3.FirstName = "Andy";
        lead3.MiddleName = "A";
        lead3.LastName = "Nevers";
        leads.Add(lead3);

        Stopwatch sw = new Stopwatch();
        sw.Start();
        string s = TestOriginal(leads);
        sw.Stop();
        Console.WriteLine("Original: {0}ms", sw.ElapsedMilliseconds);
        sw.Restart();
        string sr = ReflectionTweak(leads);
        sw.Stop();
        Console.WriteLine("Reflection tweak: {0}ms, match: {1}", sw.ElapsedMilliseconds, s == sr);
        sw.Restart();
        string sb = StringBuilderTweak(leads);
        sw.Stop();
        Console.WriteLine("StringBuilder tweak: {0}ms, match: {1}", sw.ElapsedMilliseconds, s == sb);

        //MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(s));
        Console.ReadKey();
    }

    private static string TestOriginal(List<Lead> leads)
    {
        List<string> headers = typeof(Lead).GetProperties().Select(x => x.Name).ToList();
        string s = string.Join(",", headers) + Environment.NewLine;

        for (int i = 0; i < 10000; i++)
            foreach (Lead lead in leads)
            {
                List<object> values = typeof(Lead).GetProperties().Select(prop => prop.GetValue(lead)).ToList();
                string ss = string.Join(",", values);
                s += $"{ss}{Environment.NewLine}";
            }
        return s;
    }

    private static string ReflectionTweak(List<Lead> leads)
    {
        PropertyInfo() properties = typeof(Lead).GetProperties();
        List<string> headers = properties.Select(x => x.Name).ToList();
        string s = string.Join(",", headers) + Environment.NewLine;

        for (int i = 0; i < 10000; i++)
            foreach (Lead lead in leads)
            {
                List<object> values = properties.Select(prop => prop.GetValue(lead)).ToList();
                string ss = string.Join(",", values);
                s += $"{ss}{Environment.NewLine}";
            }
        return s;
    }

    private static string StringBuilderTweak(List<Lead> leads)
    {
        StringBuilder sb = new StringBuilder();
        PropertyInfo() properties = typeof(Lead).GetProperties();
        sb.AppendLine(string.Join(",", properties.Select(x => x.Name)));

        for (int i = 0; i < 10000; i++)
            foreach (Lead lead in leads)
            {
                sb.AppendLine(string.Join(",", properties.Select(prop => prop.GetValue(lead))));
            }
        return sb.ToString();
    }

    public class Lead
    {
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
    }
}