2009-08-13 8 views
5

Je souhaite remplir les propriétés d'un objet sans utiliser de réflexion d'une manière similaire au DynamicBuilder on CodeProject. L'exemple CodeProject est conçu pour peupler des entités à l'aide d'un DataReader ou DataRecord. Je l'utilise dans plusieurs DAL pour faire bonne effet. Maintenant, je veux le modifier pour utiliser un dictionnaire ou un autre objet agnostique de données afin que je puisse l'utiliser dans le code non DAL - lieux que j'utilise actuellement la réflexion. Je ne sais presque rien sur OpCodes et IL. Je sais juste que cela fonctionne bien et est plus rapide que la réflexion.Populateur de propriété d'objet dynamique (sans réflexion)

J'ai essayé de modifier l'exemple CodeProject et à cause de mon ignorance avec IL, je suis resté coincé sur deux lignes.

  • L'un d'eux traite de dbnulls et je suis sûr que je peux perdre, mais je ne sais pas si les lignes qui précèdent et suivent ce sont liés et qui d'entre eux aussi besoin d'aller.
  • L'autre, je pense, est celui qui a tiré la valeur de l'enregistrement de données avant et doit maintenant le retirer du dictionnaire. Je pense que je peux remplacer le "getValueMethod" avec mon "property.Value" mais je ne suis pas sûr.

Je suis ouvert à d'autres façons de dépecer ce chat.

Voici le code à ce jour (les lignes commentées sont celles que je suis coincé sur):

using System; 
using System.Collections.Generic; 
using System.Reflection; 
using System.Reflection.Emit; 

public class Populator<T> 
{ 
    private delegate T Load(Dictionary<string, object> properties); 
    private Load _handler; 
    private Populator() { } 
    public T Build(Dictionary<string, object> properties) 
    { 
     return _handler(properties); 
    } 
    public static Populator<T> CreateBuilder(Dictionary<string, object> properties) 
    { 
     //private static readonly MethodInfo getValueMethod = typeof(IDataRecord).GetMethod("get_Item", new [] { typeof(int) }); 
     //private static readonly MethodInfo isDBNullMethod = typeof(IDataRecord).GetMethod("IsDBNull", new [] { typeof(int) }); 
     Populator<T> dynamicBuilder = new Populator<T>(); 
     DynamicMethod method = new DynamicMethod("Create", typeof(T), new[] { typeof(Dictionary<string, object>) }, typeof(T), true); 
     ILGenerator generator = method.GetILGenerator(); 
     LocalBuilder result = generator.DeclareLocal(typeof(T)); 
     generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes)); 
     generator.Emit(OpCodes.Stloc, result); 
     int i = 0; 
     foreach (var property in properties) 
     { 
      PropertyInfo propertyInfo = typeof(T).GetProperty(property.Key, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy | BindingFlags.Default); 
      Label endIfLabel = generator.DefineLabel(); 

      if (propertyInfo != null && propertyInfo.GetSetMethod() != null) 
      { 
       generator.Emit(OpCodes.Ldarg_0); 
       generator.Emit(OpCodes.Ldc_I4, i); 
       //generator.Emit(OpCodes.Callvirt, isDBNullMethod); 
       generator.Emit(OpCodes.Brtrue, endIfLabel); 

       generator.Emit(OpCodes.Ldloc, result); 
       generator.Emit(OpCodes.Ldarg_0); 
       generator.Emit(OpCodes.Ldc_I4, i); 
       //generator.Emit(OpCodes.Callvirt, getValueMethod); 

       generator.Emit(OpCodes.Unbox_Any, property.Value.GetType()); 
       generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod()); 
       generator.MarkLabel(endIfLabel); 
      } 
      i++; 
     } 

     generator.Emit(OpCodes.Ldloc, result); 
     generator.Emit(OpCodes.Ret); 
     dynamicBuilder._handler = (Load)method.CreateDelegate(typeof(Load)); 
     return dynamicBuilder; 
    } 
} 

EDIT:

Grâce à la mise en œuvre de PropertyDescriptor de Marc Gravell (avec HyperDescriptor) le code est simplifié cent fois. J'ai maintenant le test suivant:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using Hyper.ComponentModel; 

namespace Test 
{ 
    class Person 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 
    } 

    class Program 
    { 
     static void Main() 
     { 
      HyperTypeDescriptionProvider.Add(typeof(Person)); 
      var properties = new Dictionary<string, object> { { "Id", 10 }, { "Name", "Fred Flintstone" } }; 
      Person person = new Person(); 
      DynamicUpdate(person, properties); 
      Console.WriteLine("Id: {0}; Name: {1}", person.Id, person.Name); 
      Console.ReadKey(); 
     } 

     public static void DynamicUpdate<T>(T entity, Dictionary<string, object> properties) 
     { 
      foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(typeof(T))) 
       if (properties.ContainsKey(propertyDescriptor.Name)) 
        propertyDescriptor.SetValue(entity, properties[propertyDescriptor.Name]); 
     } 
    } 
} 

Les commentaires sur des considérations de performance pour les deux TypeDescriptor.GetProperties() & PropertyDescriptor.SetValue() sont les bienvenus ...

+0

(a répondu à un commentaire) –

Répondre

9

Edit: tout cela est fondamentalement ce pimpant fait - mais dapper est beaucoup plus optimisé. Si j'écrivais cette réponse aujourd'hui, je lirais simplement: "use dapper".


Si vous n'êtes pas énormement « up » sur IL, il existe des alternatives que vous obtenez la vitesse de l'IL et la commodité de la réflexion.

Premier exemple:

HyperDescriptor - utilise un modèle personnalisé PropertyDescriptor qui traite de l'IL pour vous, tout ce que vous avez est code comme (plus le one-liner pour permettre HyperDescriptor):

public static IEnumerable<T> Read<T>(IDataReader reader) where T : class, new() 
{ 
    PropertyDescriptorCollection props = 
     TypeDescriptor.GetProperties(typeof(T)); 

    PropertyDescriptor[] propArray = new PropertyDescriptor[reader.FieldCount]; 
    for (int i = 0; i < propArray.Length; i++) 
    { 
     propArray[i] = props[reader.GetName(i)]; 
    } 
    while(reader.Read()) { 
     T item = new T(); 
     for (int i = 0; i < propArray.Length; i++) 
     { 
      object value = reader.IsDBNull(i) ? null : reader[i]; 
      propArray[i].SetValue(item, value); 
     } 
     yield return item; 
    } 
} 

Deuxième exemple:

expressions LINQ - assez long, mais j'ai discuté de cela (et ce qui précède, il s'avère) sur usenet - voir this archive.

+0

brillant!Pouvez-vous expliquer pourquoi je dois activer HyperDescriptor? J'ai modifié votre exemple de code ici pour se débarrasser du lecteur de données et j'ai simulé des tests avec et sans HyperDescriptor. Depuis, je veux seulement définir des propriétés avec un accesseur public, il semble que je n'ai pas besoin d'HyperDescriptor. --ou ai-je manqué quelque chose? – grenade

+1

HyperDescriptor est la colle magique qui le rend rapide. Sinon, c'est essentiellement la réflexion enveloppée. HyperDescriptor écrit l'IL ** personnalisé, donc vous n'avez pas besoin de **, et le fait ressembler * exactement * au modèle 'PropertyDescriptor' classique. Ce qui est gentil. –

+0

dohhhhhhhhhhhhhhhhhhhhh! – grenade

0

Ouais, vous pouvez utiliser le code comme ceci:

for (int i = 0; i < dataRecord.FieldCount; i++) 
       { 

        PropertyInfo propertyInfo = t.GetProperty(dataRecord.GetName(i)); 
        LocalBuilder il_P = generator.DeclareLocal(typeof(PropertyInfo)); 

        Label endIfLabel = generator.DefineLabel(); 

.... ...

Questions connexes