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 \r\n
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!!!
By the way, you may write the output directly to MemoryStream
with StreamWriter
.
StreamWriterTweak
private static MemoryStream StreamWriterTweak(List<Lead> leads)
{
MemoryStream ms = new MemoryStream();
// Encoding.UTF8 produces stream with BOM, new UTF8Encoding(false) - without BOM
using (StreamWriter sw = new StreamWriter(ms, new UTF8Encoding(false), 8192, true))
{
PropertyInfo[] properties = typeof(Lead).GetProperties();
sw.WriteLine(string.Join(",", properties.Select(x => x.Name)));
for (int i = 0; i < 10000; i++)
foreach (Lead lead in leads)
{
sw.WriteLine(string.Join(",", properties.Select(prop => prop.GetValue(lead))));
}
}
ms.Position = 0;
return ms;
}
Must be fast too...
sw.Restart();
MemoryStream stream = StreamWriterTweak(leads);
sw.Stop();
Console.WriteLine("StreamWriter tweak: {0}ms, match: {1}", sw.ElapsedMilliseconds, s == Encoding.UTF8.GetString(stream.ToArray()));
Output
StreamWriter tweak: 28ms, match: True
Yes!
The fastest
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));
sw.Restart();
MemoryStream stream = StreamWriterTweak(leads);
sw.Stop();
Console.WriteLine("StreamWriter tweak: {0}ms, match: {1}", sw.ElapsedMilliseconds, s == Encoding.UTF8.GetString(stream.ToArray()));
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();
}
private static MemoryStream StreamWriterTweak(List<Lead> leads)
{
MemoryStream ms = new MemoryStream();
// Encoding.UTF8 produces stream with BOM, new UTF8Encoding(false) - without BOM
using (StreamWriter sw = new StreamWriter(ms, new UTF8Encoding(false), 8192, true))
{
PropertyInfo[] properties = typeof(Lead).GetProperties();
sw.WriteLine(string.Join(",", properties.Select(x => x.Name)));
for (int i = 0; i < 10000; i++)
foreach (Lead lead in leads)
{
sw.WriteLine(string.Join(",", properties.Select(prop => prop.GetValue(lead))));
}
}
ms.Position = 0;
return ms;
}
public class Lead
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
}
}
Lead
class to the question to make me able to run the code. Btw, CsvHelper targets .NET Standard 2.1. \$\endgroup\$MemoryStream
because it's to save the results without saving the result to disk. I need to send this Stream inside of an HTTP request. \$\endgroup\$