我正在尝试构建一个流体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。
跟进#1
我将对IImportTarget的引用更正为ITarget。对此感到抱歉。
我没有解释整个系统,因为它相当大,而且具体问题 - 你如何让两个ParameterExpressions引用同一个对象,但是它们是不同的类型(它们通过公共接口相关联 - )应该会在很多地方出现。
以下是确切的异常消息:
发生System.ArgumentException HResult = -2147024809
Message = ParameterExpression类型&#39; ConsoleApp1.TestTarget&#39;不可能是 用于类型&#39; ImportFramework.IImportTarget&#39;
的委托参数 Source = System.Core StackTrace: 在System.Linq.Expressions.Expression.ValidateLambdaArgs(类型delegateType,Expression&amp; body,ReadOnlyCollection1 parameters) at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable
1个参数) 在System.Linq.Expressions.Expression.Lambda [TDelegate](表达体, 布尔值tailCall,IEnumerable1 parameters) at ImportFramework.ImportAgentExtensions.SetProperty[TEntity,TProperty](TEntity target, Expression
1 memberLambda,Expression`1转换器)in C:\程序\ ConnellCampaigns的\ src \ ImportFramework \ ImportAgent.cs:行 55 InnerException:
答案 0 :(得分:0)
该解决方案,或至少&gt;&gt;&lt;&lt;&lt;&lt;解决方案:),是要更改将异常抛出到以下内容的最后一行:
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扩展方法控制添加到其中的内容,因此在实践中它不是问题。