2010-01-11 3 views
5

Я читаю CSV-файл, и записи записываются как строка []. Я хочу взять каждую запись и преобразовать ее в пользовательский объект.Нужен лучший способ, чем отражение

T GetMyObject<T>(); 

В настоящее время я делаю это через отражение, которое очень медленно. Я тестирую файл размером 515 мегабайт с несколькими миллионами записей. Для анализа требуется менее 10 секунд. Требуется менее 20 секунд для создания пользовательских объектов с использованием ручных преобразований с Convert.ToSomeType, но около 4 минут, чтобы сделать преобразование объектов через отражение.

Что такое хороший способ справиться с этим автоматически?

Похоже, что много времени проводится в методе PropertyInfo.SetValue. Я попробовал кешировать свойства setter и использовать это вместо этого, но это было фактически медленнее.

Я также попытался преобразовать это в делегата, такого как великий Джон Скит, предложенный здесь: Improving performance reflection , what alternatives should I consider, но проблема в том, что я не знаю, какой тип свойства опережает время. Я в состоянии получить делегат

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()); 
} 

Проблема здесь, я не могу бросить делегат в конкретный тип как Action<T, int>, так как тип собственности int не известно заранее.

+8

Эти времена не звучат плохо для меня в 500-Гбайт-файле! – Cocowalla

+0

Согласны, результаты тестов, которые вы указали, кажутся разумными. –

+3

Извините за то, что не смог помочь, но 10 секунд для разбора 500-гигабайтного файла невероятно быстро. –

ответ

7

Первое, что я бы сказал, это написать пример кода вручную, который расскажет вам, какой абсолютный лучший случай вы можете ожидать, - посмотрите, действительно ли ваш текущий код стоит исправления.

Если вы используете PropertyInfo.SetValue и т.д., то абсолютно вы можете сделать это быстрее, даже с выступах object - HyperDescriptor может быть хорошим началом (это значительно быстрее, чем сырым отражения, но не делая код усложнять) ,

Для оптимальной работы динамические методы ИИ - это путь (предварительно скомпилированный один раз); в 2.0/3.0, возможно DynamicMethod, но в 3.5 я бы предпочел ExpressionCompile()). Дайте мне знать, хотите ли вы более подробно?


Реализация с помощью Expression и CsvReader, который использует заголовки столбцов, чтобы обеспечить отображение (оно изобретает некоторые данные по той же схеме); он использует IEnumerable<T> в качестве возвращаемого типа, чтобы избежать необходимости буферизации данных (так как вы, кажется, есть довольно много его):

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(); 
     } 
    } 
} 

Вторая версия (см комментарии), который использует TypeConverter вместо 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

Вы не можете делать ссылки в выражениях. – adrianm

+0

Для новых объектов вы можете (что мы здесь делаем) - как вы думаете, работают ли lambdas, такие как 'new {Name = x.Name, Id = x.Id}'. –

+0

Я вижу, как использование DynamicMethod будет работать по ссылке Дарина Димитрова. Как бы вы использовали выражение для этого? Я могу увидеть создание файла сопоставления, в котором вы скажете, какое поле отображается для какого свойства, выполнив Map (m => m.FirstName, «FirstName»), например FluentNHibernate. Это то, о чем вы думали, или что-то еще? Я бы предпочел не создавать для этого еще один файл. Если это так, использование DynamicMethod было бы лучше. –

1

Вы должны сделать DynamicMethod или дерево выражений и создать статически типизированный код во время выполнения.

Это приведет к довольно большой стоимости установки, но никаких накладных расходов на объект не будет.
Однако это несколько сложно сделать, и это приведет к сложному отладке, который трудно отлаживать.

Смежные вопросы