mercredi 20 mai 2015

c#: Call boxed delegate

In my project I need to transform data between several classes so I created a class DataMapper that is used for strong-typed mapping of properties from two different classes. When properties in the pair need to be modified I store two delegates (converters) for this purpose.

Then the DataMapper has two methods Update(T source, S target) and Update(S source, T target) that use these mappings to provide the tranformation.

public class DataMapper<TSourceType, TTargetType> : IDataUpdater<TSourceType, TTargetType> {

    private readonly IDictionary<PropertyInfo, PropertyInfo> _sourceToTargetMap = new Dictionary<PropertyInfo, PropertyInfo>();
    private readonly IDictionary<PropertyInfo, object> _converters = new Dictionary<PropertyInfo, object>();

    public DataMapper<TSourceType, TTargetType> Map<TSourceValue, TTargetValue>(Expression<Func<TSourceType, TSourceValue>> sourcePropExpr, Expression<Func<TTargetType, TTargetValue>> targetPropExpr) {
        _sourceToTargetMap.Add(sourcePropExpr.AsPropertyInfo(), targetPropExpr.AsPropertyInfo());
        return this;
    }

    public DataMapper<TSourceType, TTargetType> Map<TSourceValue, TTargetValue>(Expression<Func<TSourceType, TSourceValue>> sourcePropExpr, Expression<Func<TTargetType, TTargetValue>> targetPropExpr,
    Func<TSourceValue, TTargetValue> sourceToTargetConverter, Func<TTargetValue, TSourceValue> targetToSourceConverter) {
        _sourceToTargetMap.Add(sourcePropExpr.AsPropertyInfo(), targetPropExpr.AsPropertyInfo());
        _converters.Add(sourcePropExpr.AsPropertyInfo(), sourceToTargetConverter);
        _converters.Add(targetPropExpr.AsPropertyInfo(), targetToSourceConverter);
        return this;
    }

    public void Update(TSourceType source, TTargetType target) {
        foreach (var keyValuePair in _sourceToTargetMap) {
            var sourceProp = keyValuePair.Key;
            var targetProp = keyValuePair.Value;
            Update(source, target, sourceProp, targetProp);
        }
    }

    public void Update(TTargetType source, TSourceType target) {
        foreach (var keyValuePair in _sourceToTargetMap) {
            var sourceProp = keyValuePair.Value;
            var targetProp = keyValuePair.Key;
            Update(source, target, sourceProp, targetProp);
        }
    }

    private void Update(object source, object target, PropertyInfo sourceProperty, PropertyInfo targetProperty) {
        var sourceValue = sourceProperty.GetValue(source);
        if (_converters.ContainsKey(sourceProperty)) {
            sourceValue = typeof(InvokeHelper<,>)
                .MakeGenericType(sourceProperty.PropertyType, targetProperty.PropertyType)
                .InvokeMember("Call", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, null, new[] { _converters[sourceProperty], sourceValue });
        }
        targetProperty.SetValue(target, sourceValue);
    }
}

Here is the usage:

public SomeClass {
    private static readonly DataMapper<SomeClass, SomeOtherClass> _dataMapper = new DataMapper<SomeClass, SomeOtherClass>()
        .Map(x => x.PropertyA, y => y.PropertyAA)
        .Map(x => x.PropertyB, y => y.PropertyBB, x => Helper.Encrypt(x), y => Helper.Decrypt(y));

    public SomeClass() {}

    public SomeClass(SomeOtherClass source) {
        if (source == null) return;
        LoadFrom(source);
    }

    public string PropertyA { get; set; }
    public string PropertyB { get; set; }

    public void LoadFrom(SomeOtherClass source) {
        _dataMapper.Update(source, this);
    }

    public void SaveTo(SomeOtherClass target) {
        _dataMapper.Update(this, target);
    }
}

You can see in class DataHelper in the last overload of method Update that when I want to call the stored converter function, I use helper class InvokeHelper, because I didn't found other way how to call boxed delegate Func. Code for class InvokeHelper is simple - just single static method:

public static class InvokeHelper<TSource, TTarget> {
    public static TTarget Call(Func<TSource, TTarget> converter, TSource source) {
        return converter(source);
    }
}

Is there a way how to do it without reflection? I need to optimalize these transformations for speed.

Thanks.





Aucun commentaire:

Enregistrer un commentaire