2017-02-18 63 views
1

我正試圖建立一個流體API來通過表達式樹來設置對象的屬性值。而做到這一點:C#表達式樹:將實體參數投射到接口

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靜態方法取得了一些進展,把我遇到的一個問題在這裏我需要指向同一個對象作爲一個特定的類(目標,在我的例子)的兩個實例,並作爲ITarget:在執行的SetProperty的最後一行出現

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

的問題。編譯器不會接受第二個參數 - 從convMethod的參數派生的參數 - 因爲就它而言,TEntity!= ITarget。

當然,除了我的示例中的TEntity - Target,它被定義爲實現ITarget :)。

我認爲表達式編譯代碼正在做什麼真正嚴格的類型檢查,而不是看一個參數是否代表可以轉換成需要的東西。

但我不知道如何將ParameterExpression轉換爲不同的類型,同時仍然指向相同的參數。我嘗試了Expression.Convert(),但這不起作用,因爲它返回一個UnaryExpression,Expression.Lambda調用不會將其作爲ParameterExpression。

跟進#1

我糾正了參考IImportTarget,是ITarget。對此感到抱歉。

我沒有解釋這是整個系統的一部分,因爲它是相當大的,具體的問題 - 你如何有兩個ParameterExpressions引用同一個對象,但是是不同的類型(它們通過一個公共接口) - 是應該在很多地方出現的東西。

這裏是確切的異常信息:出現

System.ArgumentException的HResult = -2147024809
消息=不能用於類型的代表參數類型的ParameterExpression 'ConsoleApp1.TestTarget' 「ImportFramework.IImportTarget 「
源= System.Core程序堆棧跟蹤: 在System.Linq.Expressions.Expression.ValidateLambdaArgs(類型delegateType,表達&體,ReadOnlyCollection 1 parameters) at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable 1個參數) 在System.Linq.Expression s .Expression.Lambda [TDelegate](表達式正文, Boolean tailCall,IEnumerable 1 parameters) at ImportFramework.ImportAgentExtensions.SetProperty[TEntity,TProperty](TEntity target, Expression 1 memberLambda,Expression`1 converter)in C:\ Programming \ ConnellCampaigns \ src \ ImportFramework \ ImportAgent。CS:行 55的InnerException:

+0

有什麼機會可以說出確切的錯誤是什麼?我也看不到你想要你的代碼做什麼。你想將任何字符串值轉換爲任何類型的對象?當時不會有更好的物體嗎?你在哪裏定義了'IImportTarget',因爲它看起來並不真正看到'ITarget'被鏈接到它,但這可能是因爲你的示例是不完整的(它也不是where子句的一部分) – Icepickle

+0

請見後續#1 –

回答

0

的解決方案,或者至少>> A < <解決方案:),是改變引發異常下面的最後一行:

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

這讓我指定實體類型(TEntity)已都當方法是編譯時間和使用時間。在ImportValues()方法中的循環中強制轉換是必要的,因爲我將setter作爲普通的舊對象存儲在與TEntity實例關聯的列表中。

這可能是有問題的,因爲人們永遠不知道對象列表中的內容。 OTOH,該列表只能通過內部接口獲得,並且我使用LinkProperty擴展方法控制添加到它的內容,所以在實踐中這不是問題。