首页 文章

C#表达式树:将实体参数转换为接口

提问于
浏览
1

我正在尝试构建一个流体API,用于通过表达式树在对象上设置属性值 . 而不是这样做:

public static class Converters
{
    public static SomeType ToSomeType( this Dictionary<string, string> values, string fieldName )
    {
        //...conversion logic
    }
}

public class Target : ITarget
{
    public SomeType Prop1 {get; set;}

    public void SetValues( Dictionary<string, string> values )
    {
        Prop1 = values.ToSomeType("fieldName");
    }
}

我希望能够这样做:

public class Target : ITarget
{
    public Target()
    {
        this.SetProperty( x=>x.Prop1, y => y.ToSomeType("fieldName") );
    }

    public void SetValues( Dictionary<string, string> values )
    {
        //...logic that executes compiled converter functions derived from
        // SetProperty calls, and which are stored in an internal list
    }
}

我在SetProperty静态方法上取得了一些进展,让我遇到了一个问题,我需要将同一个对象作为特定类的实例(在我的示例中为Target)和ITarget引用:

public static void SetProperty<TEntity, TProperty>( this TEntity target, Expression<Func<TEntity, object>> memberLambda,
        Expression<Func<IImportFile, TProperty>> converter )
        where TEntity: class, ITarget
{
    var memberSelector = memberLambda.Body as MemberExpression;
    if( memberSelector == null )
        throw new ArgumentException(
                $"{nameof( SetProperty )} -- invalid property specification on Type {typeof(TEntity).FullName}" );

    var propInfo = memberSelector.Member as PropertyInfo;

    if( propInfo == null )
        throw new ArgumentException(
                $"{nameof( SetProperty )} -- invalid property specification on Type {typeof( TEntity ).FullName}" );

    MethodCallExpression convMethod = converter.Body as MethodCallExpression;
    if( convMethod == null )
        throw new ArgumentException(
                $"{nameof( SetProperty )} -- converter does not contain a MethodCallExpression on Type {typeof( IImportFile ).FullName}" );

    ParameterExpression targetExp = Expression.Parameter( typeof(TEntity), "target" );
    MemberExpression propExp = Expression.Property( targetExp, propInfo );

    BinaryExpression assignExp = Expression.Assign( propExp, convMethod );

    // this next line throws the exception
    var junk = Expression.Lambda<Action<ITarget, IImportFile>>( assignExp, targetExp,
                (ParameterExpression) convMethod.Arguments[ 0 ] ).Compile();
}

问题出现在SetProperty实现的最后一行 . 编译器不接受第二个参数 - 从convMethod的参数派生的参数 - 因为,就其而言,TEntity!= ITarget .

当然,除了我的例子中的TEntity - Target被定义为实现ITarget :) .

我认为Expression编译代码正在执行相当严格的类型检查,而不是查看参数是否表示可以转换为需要的内容 .

但我无法弄清楚如何将ParameterExpression转换为不同的Type,同时仍然让它引用相同的参数 . 我尝试了Expression.Convert(),但这不起作用,因为它返回一个UnaryExpression,Expression.Lambda调用它不会作为ParameterExpression .

Follow Up #1

我更正了对IImportTarget的引用,将其作为ITarget . 对此感到抱歉 .

我没有解释整个系统,因为它是相当大的,并且具体的问题 - 你如何有两个ParameterExpressions引用相同的对象,但是不同的类型(通过公共接口相关) - - 应该在很多地方出现的东西 .

这是确切的异常消息:

System.ArgumentException发生HResult = -2147024809 Message = ParameterExpression类型'ConsoleApp1.TestTarget'不能用于'ImportFramework.IImportTarget'类型的委托参数Source = System.Core StackTrace:at System.Linq.Expressions.Expression.ValidateLambdaArgs(在System.Linq.Expressions.Expression.Lambda [TDelegate]的System.Linq.Expressions.Expression.Lambda [TDelegate](表达式主体,字符串名称,布尔值tailCall,IEnumerable1参数)中键入delegateType,Expression&body,ReadOnlyCollection1参数) body,Boolean tailCall,IEnumerable1参数)在ImportFramework.ImportAgentExtensions.SetProperty [TEntity,TProperty](TEntity target,Expression1 memberLambda,Expression`1转换器)中的C:\ Programming \ ConnellCampaigns \ src \ ImportFramework \ ImportAgent.cs:第55行InnerException :

1 回答

  • 0

    解决方案,或至少>>一个<<解决方案:),是改变将异常抛出到下面的最后一行:

    var junk = Expression.Lambda<Action<TEntity, IImportFile>>( assignExp, targetExp, (ParameterExpression) convMethod.Arguments[ 0 ] ).Compile();
    

    然后还更改设置值的方法以使用扩展方法:

    public static void ImportValues<TEntity>( this TEntity target, IImportFile importer )
        where TEntity : class, IImportTarget
    {
        foreach( Action<TEntity, IImportFile> setter in ( ( IImportTargetSetValues ) target ).Setters )
        {
            setter( target, importer );
        }
    }
    

    这允许我在编译方法和使用它时指定实体Type(TEntity) . 在ImportValues()方法中循环中的强制转换是必要的,因为我将setter作为普通旧对象存储在与TEntity实例关联的列表中 .

    这可能会有问题,因为人们永远不会知道对象列表中的内容 . OTOH,该列表仅通过内部接口提供,并且我使用LinkProperty扩展方法控制添加到其中的内容,因此在实践中它不是问题 .

相关问题