我正在尝试构建一个流体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 回答
解决方案,或至少>>一个<<解决方案:),是改变将异常抛出到下面的最后一行:
然后还更改设置值的方法以使用扩展方法:
这允许我在编译方法和使用它时指定实体Type(TEntity) . 在ImportValues()方法中循环中的强制转换是必要的,因为我将setter作为普通旧对象存储在与TEntity实例关联的列表中 .
这可能会有问题,因为人们永远不会知道对象列表中的内容 . OTOH,该列表仅通过内部接口提供,并且我使用LinkProperty扩展方法控制添加到其中的内容,因此在实践中它不是问题 .