【问题标题】:c#: Call boxed delegatec#: 调用盒装委托
【发布时间】:2015-05-20 15:30:43
【问题描述】:

在我的项目中,我需要在多个类之间转换数据,因此我创建了一个 DataMapper 类,用于对来自两个不同类的属性进行强类型映射。当需要修改对中的属性时,我为此目的存储了两个委托(转换器)。

那么 DataMapper 有两个方法 Update(T source, S target) 和 Update(S source, T target) 使用这些映射来提供转换。

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

用法如下:

public SomeClass {
    private static readonly IDataUpdater<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 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);
    }
}

你可以在方法Update的最后一个重载中的DataHelper类中看到,当我想调用存储的转换器函数时,我使用了帮助类InvokeHelper,因为我没有找到其他方法来调用盒装委托Func。 InvokeHelper 类的代码很简单——只有一个静态方法:

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

有没有办法在没有反思的情况下做到这一点?我需要优化这些转换以提高速度。

谢谢。

【问题讨论】:

  • 请注意,委托是引用类型,而不是值类型,因此不能装箱。
  • 您不应该遍历整个字典并进行线性搜索以查找值;它违背了使用字典的全部目的。您实际上应该利用它为特定键找到值的能力无需执行任何操作
  • Servy:好的,我写了“盒装委托”,但我提到委托作为对象持续存在(在字典 _converters 中) PS:我不明白你关于遍历整个字典的第二条评论
  • 您在字典中进行线性搜索,查看每个对象,而不是查找键的值,这不需要查看大多数对象。

标签: c# reflection delegates


【解决方案1】:

您可以使用Delegate.DynamicInvoke 来调用委托。或者,使用dynamic

((dynamic)(Delegate)_converters[sourceProperty])(sourceValue);

(Delegate) 演员表不是必需的。它用于文档和运行时断言目的。如果你不喜欢它,请忽略它。

实际上,您最好在字典中使用delegate 而不是object

【讨论】:

  • 谢谢,就是这样......当我将 Delegate 而不是对象持久化到字典 _converterters 中时,我可以调用 DynamicInvoke 的值。
【解决方案2】:

如果是我,我会使用一些带有表达式的元编码来创建一个已编译和强类型委托的列表。调用Update方法时,可以遍历列表中的每一个Action,从源更新目的地。

没有反射,所有的编译都在更新调用之前完成一次。

public class DataMapper<TSourceType, TTargetType> : IDataUpdater<TSourceType, TTargetType>
{
    List<Action<TSourceType, TTargetType>> _mappers = new List<Action<TSourceType, TTargetType>>();
    DataMapper<TTargetType, TSourceType> _reverseMapper;

    public DataMapper() : this(false) { }
    public DataMapper(bool isReverse)
    {
        if (!isReverse)
        {
            _reverseMapper = new DataMapper<TTargetType, TSourceType>(isReverse: true);
        }
    }

    public DataMapper<TSourceType, TTargetType> Map<TSourceValue, TTargetValue>(
        Expression<Func<TSourceType, TSourceValue>> sourcePropExpr,
        Expression<Func<TTargetType, TTargetValue>> targetPropExpr)
    {
        var mapExpression = Expression.Assign(targetPropExpr.Body, sourcePropExpr.Body);

        _mappers.Add(
            Expression.Lambda<Action<TSourceType, TTargetType>>(
                mapExpression,
                sourcePropExpr.Parameters[0],
                targetPropExpr.Parameters[0])
            .Compile());

        if (_reverseMapper != null) _reverseMapper.Map(targetPropExpr, sourcePropExpr);

        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)
    {
        var convertedSourceExpression = Expression.Invoke(Expression.Constant(sourceToTargetConverter), sourcePropExpr.Body);
        var mapExpression = Expression.Assign(targetPropExpr.Body, convertedSourceExpression);

        _mappers.Add(
            Expression.Lambda<Action<TSourceType, TTargetType>>(
                mapExpression,
                sourcePropExpr.Parameters[0],
                targetPropExpr.Parameters[0])
            .Compile());

        if (_reverseMapper != null) _reverseMapper.Map(targetPropExpr, sourcePropExpr, targetToSourceConverter, sourceToTargetConverter);
        return this;
    }

    public void Update(TSourceType source, TTargetType target)
    {
        foreach (var mapper in _mappers)
        {
            mapper(source, target);
        }
    }

    public void Update(TTargetType source, TSourceType target)
    {
        if (_reverseMapper != null)
        {
            _reverseMapper.Update(source, target);
        }
        else
        {
            throw new Exception("Reverse mapper is null.  Did you reverse twice?");
        };
    }
}

通过获取传入的表达式并将它们用作新表达式的一部分来构建表达式。

假设你打电话给.Map(x =&gt; x.PropertyA, y =&gt; y.PropertyAA)。您现在有 2 个表达式,每个表达式都有一个参数 xy,每个表达式都有一个主体 x.PropertyAy.PropertyAA

现在您想将这些表达式部分重新组合成一个赋值表达式,例如y.PropertyAA = x.PropertyA。这是在var mapExpression = Expression.Assign(targetPropExpr.Body, sourcePropExpr.Body); 行中完成的,它为您提供了预期的表达式。

现在,当您调用 Expression.Lambda 时,您会将参数 (x,y) 合并到一个类似于 (x,y) = &gt; y.PropertyAA = x.PropertyA 的新表达式中。

在你可以执行之前,你需要编译它,因此.Compile()。但是由于您只需要为任何给定的地图编译一次,因此您可以编译并存储结果。未编译的表达式是Expression&lt;Action&lt;TSourceType,TTargetType&gt;&gt; 类型,编译后的结果类型是Action&lt;TSourceType,TTargetType&gt;

【讨论】:

  • 嗨,Grax,感谢您的回答。我会尝试这个解决方案......现在它看起来有点复杂,因为我对创建 Lambda 表达式了解不多,但我会尝试沉浸其中。
  • 明白,我添加了更多关于表达式构建如何工作的细节。希望这会有所帮助。
猜你喜欢
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-10
  • 2013-01-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多