Proprietà oggetto dinamico Populator (senza riflessione)
-
16-09-2019 - |
Domanda
Voglio per popolare le proprietà di un oggetto senza l'utilizzo di riflessione in un modo simile a DynamicBuilder su CodeProject . L'esempio CodeProject è su misura per popolare le entità che utilizzano un DataReader o dataRecord. Io uso questo in diversi dals con buoni risultati. Ora voglio modificarlo in modo da utilizzare un dizionario o altri dati oggetto agnostico modo che io possa utilizzarlo in --places Senza codice DAL Io attualmente uso riflessione. Non so quasi nulla di OpCodes e IL. So solo che funziona bene ed è più veloce di riflessione.
Ho cercato di modificare l'esempio CodeProject ea causa della mia ignoranza con IL, ho ottenuto bloccato su due righe.
- Uno di loro si occupa di dbnulls e sono abbastanza sicuro che posso solo perdere, ma non so se le linee precedente e quello successivo sono correlati e quali di essi sarà anche bisogno di andare.
- L'altro, credo, è quello che ha tirato fuori il valore del dataRecord prima e ora ha bisogno di tirarlo fuori del dizionario. Credo di poter sostituire la "getValueMethod" con la mia "property.Value", ma non sono sicuro.
Sono aperto a alternativi / modi migliori di scuoiare questo gatto troppo.
Ecco il codice finora (le righe di commento fuori sono quelli che mi sono bloccato su):
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:
Utilizzando implementazione PropertyDescriptor di Marc Gravell (con HyperDescriptor) il codice è semplificato cento volte. Ora ho il seguente test:
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]);
}
}
}
Qualsiasi commento sulle considerazioni sulle prestazioni per entrambi i TypeDescriptor.GetProperties () e PropertyDescriptor.SetValue () sono i benvenuti ...
Soluzione
Modifica: tutto questo è fondamentalmente ciò che Dapper fa - ma elegante è molto più ottimizzato. Se stavo scrivendo questa risposta oggi, avrebbe letto semplicemente:. "Usare dapper"
Se non si è enormemente "up" su IL, ci sono alternative che si ottiene la velocità di IL e la comodità di riflessione.
Primo esempio:
HyperDescriptor - utilizza un modello PropertyDescriptor
personalizzato che si occupa della IL per voi, quindi tutto quello che hai è codice come (più l'one-liner per abilitare 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;
}
}
Secondo esempio:
espressioni LINQ - abbastanza lunghe, ma ho discusso di questo (e il di cui sopra, si scopre) per il download diretto - vedere questo archivio .
Altri suggerimenti
Si, è possibile utilizzare il codice come questo:
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();
.... ...