2010-01-11 21 views
5

Estoy leyendo un archivo CSV y los registros se registran como una cadena []. Quiero tomar cada registro y convertirlo en un objeto personalizado.Necesita una mejor manera que la Reflexión

T GetMyObject<T>(); 

Actualmente estoy haciendo esto a través de la reflexión, que es realmente lenta. Estoy probando con un archivo de 515 Meg con varios millones de registros. Toma menos de 10 segundos para analizar. Se necesitan menos de 20 segundos para crear los objetos personalizados mediante conversiones manuales con Convert.ToSomeType, pero alrededor de 4 minutos para realizar la conversión a los objetos a través de la reflexión.

¿Cuál es una buena manera de manejar esto automáticamente?

Parece que se usa mucho tiempo en el método PropertyInfo.SetValue. Intenté guardar en caché las propiedades MethodInfo setter y usar eso, pero en realidad fue más lento.

También he intentado convertir eso en un delegado como el gran Jon Skeet que se sugiere aquí: Improving performance reflection , what alternatives should I consider, pero el problema es que no sé cuál es el tipo de propiedad por adelantado. Soy capaz de obtener el delegado

var myObject = Activator.CreateInstance<T>(); 
foreach(var property in typeof(T).GetProperties()) 
{ 
    var d = Delegate.CreateDelegate(typeof(Action<,>) 
    .MakeGenericType(typeof(T), property.PropertyType), property.GetSetMethod()); 
} 

El problema aquí es que no puedo emitir el delegado en un tipo concreto como Action<T, int>, debido a que el tipo de propiedad de int no se conoce de antemano.

+8

¡Estos tiempos no me parecen malos para un archivo de 500 GB! – Cocowalla

+0

De acuerdo, los resultados de referencia que usted indicó parecen razonables. –

+3

Lo siento por no poder ayudar, pero 10s para analizar un archivo de 500 GB es increíblemente rápido. –

Respuesta

7

Lo primero que diría es escribir un código de muestra manualmente que le indique cuál es el mejor caso que puede esperar: verifique si vale la pena corregir su código actual.

Si está utilizando PropertyInfo.SetValue etc, entonces absolutamente usted puede hacer que sea más rápido, incluso con salientes object - HyperDescriptor podría ser un buen comienzo (es significativamente más rápido que la reflexión en bruto, pero sin hacer el código más complicado) .

Para un rendimiento óptimo, los métodos dinámicos de IL son el camino a seguir (precompilados una vez); en 2.0/3.0, quizás DynamicMethod, pero en 3.5 preferiría Expression (con Compile()). Déjame saber si quieres más detalles?


implementación usando Expression y CsvReader, que utiliza los encabezados de columna para proporcionar el mapeo (inventa algunos datos a lo largo de las mismas líneas); que utiliza IEnumerable<T> como el tipo de retorno para evitar tener que amortiguar los datos (ya que parecen tener mucho de él):

using System; 
using System.Collections.Generic; 
using System.Globalization; 
using System.IO; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 
using LumenWorks.Framework.IO.Csv; 
class Entity 
{ 
    public string Name { get; set; } 
    public DateTime DateOfBirth { get; set; } 
    public int Id { get; set; } 

} 
static class Program { 

    static void Main() 
    { 
     string path = "data.csv"; 
     InventData(path); 

     int count = 0; 
     foreach (Entity obj in Read<Entity>(path)) 
     { 
      count++; 
     } 
     Console.WriteLine(count); 
    } 
    static IEnumerable<T> Read<T>(string path) 
     where T : class, new() 
    { 
     using (TextReader source = File.OpenText(path)) 
     using (CsvReader reader = new CsvReader(source,true,delimiter)) { 

      string[] headers = reader.GetFieldHeaders(); 
      Type type = typeof(T); 
      List<MemberBinding> bindings = new List<MemberBinding>(); 
      ParameterExpression param = Expression.Parameter(typeof(CsvReader), "row"); 
      MethodInfo method = typeof(CsvReader).GetProperty("Item",new [] {typeof(int)}).GetGetMethod(); 
      Expression invariantCulture = Expression.Constant(
       CultureInfo.InvariantCulture, typeof(IFormatProvider)); 
      for(int i = 0 ; i < headers.Length ; i++) { 
       MemberInfo member = type.GetMember(headers[i]).Single(); 
       Type finalType; 
       switch (member.MemberType) 
       { 
        case MemberTypes.Field: finalType = ((FieldInfo)member).FieldType; break; 
        case MemberTypes.Property: finalType = ((PropertyInfo)member).PropertyType; break; 
        default: throw new NotSupportedException(); 
       } 
       Expression val = Expression.Call(
        param, method, Expression.Constant(i, typeof(int))); 
       if (finalType != typeof(string)) 
       { 
        val = Expression.Call(
         finalType, "Parse", null, val, invariantCulture); 
       } 
       bindings.Add(Expression.Bind(member, val)); 
      } 

      Expression body = Expression.MemberInit(
       Expression.New(type), bindings); 

      Func<CsvReader, T> func = Expression.Lambda<Func<CsvReader, T>>(body, param).Compile(); 
      while (reader.ReadNextRecord()) { 
       yield return func(reader); 
      } 
     } 
    } 
    const char delimiter = '\t'; 
    static void InventData(string path) 
    { 
     Random rand = new Random(123456); 
     using (TextWriter dest = File.CreateText(path)) 
     { 
      dest.WriteLine("Id" + delimiter + "DateOfBirth" + delimiter + "Name"); 
      for (int i = 0; i < 10000; i++) 
      { 
       dest.Write(rand.Next(5000000)); 
       dest.Write(delimiter); 
       dest.Write(new DateTime(
        rand.Next(1960, 2010), 
        rand.Next(1, 13), 
        rand.Next(1, 28)).ToString(CultureInfo.InvariantCulture)); 
       dest.Write(delimiter); 
       dest.Write("Fred"); 
       dest.WriteLine(); 
      } 
      dest.Close(); 
     } 
    } 
} 

Segunda versión (ver comentarios) que utiliza TypeConverter en lugar de Parse:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Globalization; 
using System.IO; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 
using LumenWorks.Framework.IO.Csv; 
class Entity 
{ 
    public string Name { get; set; } 
    public DateTime DateOfBirth { get; set; } 
    public int Id { get; set; } 

} 
static class Program 
{ 

    static void Main() 
    { 
     string path = "data.csv"; 
     InventData(path); 

     int count = 0; 
     foreach (Entity obj in Read<Entity>(path)) 
     { 
      count++; 
     } 
     Console.WriteLine(count); 
    } 
    static IEnumerable<T> Read<T>(string path) 
     where T : class, new() 
    { 
     using (TextReader source = File.OpenText(path)) 
     using (CsvReader reader = new CsvReader(source, true, delimiter)) 
     { 

      string[] headers = reader.GetFieldHeaders(); 
      Type type = typeof(T); 
      List<MemberBinding> bindings = new List<MemberBinding>(); 
      ParameterExpression param = Expression.Parameter(typeof(CsvReader), "row"); 
      MethodInfo method = typeof(CsvReader).GetProperty("Item", new[] { typeof(int) }).GetGetMethod(); 

      var converters = new Dictionary<Type, ConstantExpression>(); 
      for (int i = 0; i < headers.Length; i++) 
      { 
       MemberInfo member = type.GetMember(headers[i]).Single(); 
       Type finalType; 
       switch (member.MemberType) 
       { 
        case MemberTypes.Field: finalType = ((FieldInfo)member).FieldType; break; 
        case MemberTypes.Property: finalType = ((PropertyInfo)member).PropertyType; break; 
        default: throw new NotSupportedException(); 
       } 
       Expression val = Expression.Call(
        param, method, Expression.Constant(i, typeof(int))); 
       if (finalType != typeof(string)) 
       { 
        ConstantExpression converter; 
        if (!converters.TryGetValue(finalType, out converter)) 
        { 
         converter = Expression.Constant(TypeDescriptor.GetConverter(finalType)); 
         converters.Add(finalType, converter); 
        } 
        val = Expression.Convert(Expression.Call(converter, "ConvertFromInvariantString", null, val), 
         finalType); 
       } 
       bindings.Add(Expression.Bind(member, val)); 
      } 

      Expression body = Expression.MemberInit(
       Expression.New(type), bindings); 

      Func<CsvReader, T> func = Expression.Lambda<Func<CsvReader, T>>(body, param).Compile(); 
      while (reader.ReadNextRecord()) 
      { 
       yield return func(reader); 
      } 
     } 
    } 
    const char delimiter = '\t'; 
    static void InventData(string path) 
    { 
     Random rand = new Random(123456); 
     using (TextWriter dest = File.CreateText(path)) 
     { 
      dest.WriteLine("Id" + delimiter + "DateOfBirth" + delimiter + "Name"); 
      for (int i = 0; i < 10000; i++) 
      { 
       dest.Write(rand.Next(5000000)); 
       dest.Write(delimiter); 
       dest.Write(new DateTime(
        rand.Next(1960, 2010), 
        rand.Next(1, 13), 
        rand.Next(1, 28)).ToString(CultureInfo.InvariantCulture)); 
       dest.Write(delimiter); 
       dest.Write("Fred"); 
       dest.WriteLine(); 
      } 
      dest.Close(); 
     } 
    } 
} 
+0

No se puede hacer una asignación en expresiones. – adrianm

+0

Para objetos nuevos que puedas (que es lo que estamos haciendo aquí), ¿cómo crees que funcionan las lambdas, como 'new {Name = x.Name, Id = x.Id}'? –

+0

Veo cómo el uso de DynamicMethod funcionaría desde el enlace de Darin Dimitrov. ¿Cómo usarías Expression para hacer esto? Puedo ver cómo se crea un archivo de mapeo donde se dice qué campo se asigna a qué propiedad haciendo Map (m => m.FirstName, "FirstName") como lo hace FluentNHibernate. ¿Eso es lo que estabas pensando, o algo más? Preferiría no querer crear otro archivo para esto. Si ese es el caso, usar DynamicMethod sería mejor. –

1

Debe crear un DynamicMethod o un árbol de expresiones y compilar código estáticamente tipado en el tiempo de ejecución.

Esto implicará un costo de configuración bastante grande, pero no por sobrecarga de objetos en absoluto.
Sin embargo, es algo difícil de hacer y resultará en código complicado que es difícil de depurar.

Cuestiones relacionadas