Object to object mapperi kirjutamine
13.02.2010 | Gunnar
Hiljuti treisin kirjatüki stringide pildumisest ja enne seda kirjutasin sellest, kuidas objektide omadusi kiiresti kopeerida. Nüüd peale mõningast mängimist ja katsetamist kirjutasin kokku rivi klasse, millest siin avaldan selle kõikse kiirema.
Objektide omaduste kopeerimise kanne annab hea ülevaate sellest, mis tehtud ja proovitud sai. Tegin sellesse koodi mõningad kohendused, koostasin klassid ja võrdlesin tulemust AutoMapperiga.
NB! Minu siintoodud koodi suhtu kriitiliselt. Ära käitu nagu keskmine ALT.NET-i fanboy, kes vanaisade jutust huvitavama osa ära kuulab, manitsused kõrvust mööda laseb ja seejärel endale tundmatute vahenditega hea projekti põhja laseb. Mõtle kaks sammu ette. Ja kui ennast kindlalt ei tunne, siis võta AutoMapper – see on palju küpsem ja stabiilsem ja testitum toode, kui minu siinne kritseldus.
Et mapperi implementatsioonide arvu mitte piirata, siis tekitasin abstraktse baasklassi, mis pakub välja põhilise funktsionaalsuse.
{
public PropertyInfo SourceProperty { get; set; }
public PropertyInfo TargetProperty { get; set; }
}
public abstract class ObjectCopyBase
{
public abstract void MapTypes<T, TU>();
public abstract void Copy<T, TU>(T source, TU target);
protected virtual IList<PropertyMap>
GetMatchingProperties<T, TU>()
{
var sourceProperties = typeof(T).GetProperties();
var targetProperties = typeof(TU).GetProperties();
var properties = (from s in sourceProperties
from t in targetProperties
where s.Name == t.Name &&
s.CanRead &&
t.CanWrite &&
s.PropertyType == t.PropertyType
select new PropertyMap
{
SourceProperty = s,
TargetProperty = t
}).ToList();
return properties;
}
protected virtual string GetMapKey<T, TU>()
{
var className = "Copy_";
className += typeof(T).FullName.Replace(".", "_");
className += "_";
className += typeof(TU).FullName.Replace(".", "_");
return className;
}
}
Selle peale ehitasin LCG (Lightweigh Code Generation) kasutava implementatsiooni mapperist. Välja näeb see selline.
{
private delegate void CopyPublicPropertiesDelegate<T, TU>
(T source, TU target);
private readonly Dictionary<string, object> _del =
new Dictionary<string, object>();
public override void MapTypes<T, TU>()
{
var key = GetMapKey<T, TU>();
if (_del.ContainsKey(key))
return;
var source = typeof(T);
var target = typeof(TU);
var args = new[] { source, target };
var mod = typeof(Program).Module;
var dm = new DynamicMethod(key, null, args, mod);
var il = dm.GetILGenerator();
var maps = GetMatchingProperties<T, TU>();
foreach (var map in maps)
{
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Callvirt,
map.SourceProperty.GetGetMethod(), null);
il.EmitCall(OpCodes.Callvirt,
map.TargetProperty.GetSetMethod(), null);
}
il.Emit(OpCodes.Ret);
var del = dm.CreateDelegate(
typeof(CopyPublicPropertiesDelegate<T, TU>));
_del.Add(key, del);
}
public override void Copy<T, TU>(T source, TU target)
{
var key = GetMapKey<T, TU>();
var del = (CopyPublicPropertiesDelegate<T, TU>)_del[key];
del.Invoke(source, target);
}
}
Koodi polegi kuigi palju ja see muudab klassi kasutamise küllaltki lihtsaks ja hästi kontrollitavaks.
Aga võrdleme tulemusi AutoMapperiga. Minul olid paar objekti, mis polnud küll keerukamate killast, kuid erinevus tuli siiski päris hea.
Mu daamid ja härrad – erinevus on ~6 kordne!
Okay, see võrdlus pole kuigi aus, sest AutoMapper teeb veel palju muid häid asju ära, mida minu mapper ei tee ja arvatavasti ei hakkagi tegema. Samas on see siin hea näide sellest, kuidas algne sopakood millekski ilusamaks vormida ja kuidas lihtsad asjad annavad paremaid tulemusi kui keerukad asjad.
P.S. Kui keegi soovib üleelatust ülevaadet alates nullist kuni selle tulemuseni, siis palun lahkesti märku anda.
