2
\$\begingroup\$

So, I'm building export/import CSV helper. I have some performance issues in the code below. it takes me to parse CSV of 25,000 rows at 7 seconds. If someone can help, it will be awesome!

public System.IO.Stream ParseContent<T>(IEnumerable<T> entities) where T : class
 {
 if (entities == null)
 throw new ArgumentException(nameof(entities), "List accepted is empty.");
 Type type = entities.First().GetType();
 PropertyInfo[] properties = type.GetProperties();
 string headers = GenerateTemplate(properties);
 //No headers accepted - cannot export the content
 if (string.IsNullOrEmpty(headers))
 return null;
 string contentToExport = $"{headers}{NewLineDelimiter}";
 foreach (T entity in entities)
 {
 if (entity == null)
 continue;
 string template = this.ExportLine(entity, properties);
 contentToExport += $"{template}{NewLineDelimiter}";
 }
 byte[] bytes = System.Text.Encoding.UTF8.GetBytes(contentToExport);
 System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(bytes);
 return memoryStream;
 }
 private string ExportLine<T>(T entity, PropertyInfo[] properties) where T : class
 {
 if (entity == null || properties == null)
 return string.Empty;
 string template = "";
 foreach (PropertyInfo property in properties)
 {
 string value = null;
 if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
 {
 Type underlyingType = property.PropertyType.GetGenericArguments()[0];
 if (underlyingType.IsValueType || underlyingType == typeof(string))
 {
 System.Collections.IEnumerable list = (System.Collections.IEnumerable)property.GetValue(entity);
 value = string.Join(EnumerableValueDelimiter, list.Cast<string>());
 }
 }
 else if (property.PropertyType.IsClass && (!property.PropertyType.IsPrimitive && !property.PropertyType.IsEnum) && property.PropertyType != typeof(string))
 {
 //Object type. need to be serialized
 object propertyValue = property.GetValue(entity);
 if (propertyValue != null)
 value = JsonConvert.SerializeObject(propertyValue);
 else
 value = "null";
 }
 else
 {
 value = property.GetValue(entity)?.ToString();
 }
 if (string.IsNullOrEmpty(value))
 value = "";
 template += $"{value}{LineValuesDelimiter}";
 }
 //Removing the last delimiter at the row.
 if (template.Length > 0)
 template = template.Remove(template.Length - 1, 1);
 return template;
 }
t3chb0t
44.6k9 gold badges84 silver badges190 bronze badges
asked Dec 13, 2018 at 11:30
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

ParseContent()

  • Type type = entities.First().GetType(); can throw an exception if entities doesn't contain any items. I may be wrong but you could use the T as well like Type type = typeof(T);.
  • If entities is null an ArgumentNullException should be thrown instead of an ArgumentException.
  • The foreach could be simplified and you should use a StringBuilder instead of concating strings in a loop. Thats because strings are immutable and for each contentToExport += $"{template}{NewLineDelimiter}"; you create a new string object.
  • If the right hand side of an assignment makes the type clear one should use var instead of the concrete type.
  • Omitting braces {} althought they might be optional can lead to hidden and therefor hard to find bugs. I would like to encourage you to always use them.
  • Having a variable memoryStream doesn't buy you anything. Just return the new memorystream.

Applying these points will lead to

public System.IO.Stream ParseContent<T>(IEnumerable<T> entities) where T : class
{
 if (entities == null)
 {
 throw new ArgumentNullException(nameof(entities), "List accepted is empty.");
 }
 if (!entities.Any())
 {
 //assuming thats the desired behaviour
 return null;
 }
 Type type = typeof(T);
 PropertyInfo[] properties = type.GetProperties();
 string headers = GenerateTemplate(properties);
 //No headers accepted - cannot export the content
 if (string.IsNullOrEmpty(headers))
 {
 return null;
 }
 StringBuilder contentToExport = new StringBuilder( $"{headers}{NewLineDelimiter}");
 foreach (T entity in entities.Where(e=>e!=null))
 {
 string template = this.ExportLine(entity, properties);
 contentToExport.Append($"{template}{NewLineDelimiter}");
 }
 byte[] bytes = System.Text.Encoding.UTF8.GetBytes(contentToExport.ToString());
 return new System.IO.MemoryStream(bytes);
} 
answered Dec 13, 2018 at 12:01
\$\endgroup\$
2
  • \$\begingroup\$ I knew strings are immutables, didn't think that it would cause that performance. Thank you very much \$\endgroup\$ Commented Dec 13, 2018 at 12:56
  • 1
    \$\begingroup\$ Some small additions: It's generally not advised to enumerate an IEnumerable more than once, it may not support multiple enumerations or it could be backed by an expensive database query. typeof(T) and entities.First().GetType() can be different types and not every element in entities is guaranteed to be the same type as entities.First().GetType(). \$\endgroup\$ Commented Dec 13, 2018 at 13:13

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.