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;
    }
\$\endgroup\$
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);
}  
\$\endgroup\$
  • \$\begingroup\$ I knew strings are immutables, didn't think that it would cause that performance. Thank you very much \$\endgroup\$ – o.Nassie Dec 13 '18 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\$ – Johnbot Dec 13 '18 at 13:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.