2013-01-17 58 views
4

我的目標是使用Lambdas創建可以安全檢索深度屬性值的屬性綁定對象。通過安全的方式,如果前面的屬性之一爲空而不是拋出空引用異常,則它返回屬性類型的默認值。使用表達式從Lambda創建安全的深度屬性訪問器

方法簽名:

public static Func<TO, TP> BuildSafeAccessor<TO, TP>(this Expression<Func<TO, TP>> propertyExpression) where TO: class 
{ 
} 

*編輯:澄清我的問題

所以,如果我打電話:

var safeAccessor = BuildSafeAccessor<Person>(p => p.Address.Zip); 

當safeAccessor被調用時,它的邏輯是以下幾點:

if (p.Address == null) 
    return default(TP); 
return p.Address.Zip; 
+0

你能明白嗎?這裏的*問題*是什麼? –

+0

我想知道如何構建將編譯成安全訪問器的表達式。我的BuildSafeAccessor方法的正文。 –

+0

我不斷遇到如何構建表達式的問題,特別是Expression.Condition(test,ifTrue,ifFalse),我需要已經完全創建的ifFalse表達式。可能有一些工作在一點。 –

回答

3

這裏的關鍵是你不需要「已經完全創建了ifFalse表達式」,你可以遞歸地構建它。

的代碼看起來是這樣的:

public static Func<TO, TP> BuildSafeAccessor<TO, TP>(Expression<Func<TO, TP>> propertyExpression) 
{ 
    var properties = GetProperties(propertyExpression.Body); 
    var parameter = propertyExpression.Parameters.Single(); 
    var nullExpression = Expression.Constant(default(TP), typeof(TP)); 

    var lambdaBody = BuildSafeAccessorExpression(parameter, properties, nullExpression); 
    var lambda = Expression.Lambda<Func<TO, TP>>(lambdaBody, parameter); 

    return lambda.Compile(); 
} 

private static Expression BuildSafeAccessorExpression(Expression init, IEnumerable<PropertyInfo> properties, Expression nullExpression) 
{ 
    if (!properties.Any()) 
     return init; 

    var propertyAccess = Expression.Property(init, properties.First()); 
    var nextStep = BuildSafeAccessorExpression(propertyAccess, properties.Skip(1), nullExpression); 

    return Expression.Condition(
     Expression.ReferenceEqual(init, Expression.Constant(null)), nullExpression, nextStep); 
} 

private static IEnumerable<PropertyInfo> GetProperties(Expression expression) 
{ 
    var results = new List<PropertyInfo>(); 

    while (expression is MemberExpression) 
    { 
     var memberExpression = (MemberExpression)expression; 
     results.Add((PropertyInfo)memberExpression.Member); 
     expression = memberExpression.Expression; 
    } 

    if (!(expression is ParameterExpression)) 
     throw new ArgumentException(); 

    results.Reverse(); 

    return results; 
} 

(注意,此代碼使用LINQ低效,使其更具可讀性)

如果您在拉姆達a => a.B.C.D運行它,它會創建表達式(表示ToString()在其上的結果):

a => IIF((a == null), null, IIF((a.B == null), null, IIF((a.B.C == null), null, a.B.C.D))) 

注意,這個訪問a.B最多三次。如果該財產緩慢或有副作用,那可能是一個問題。如果這對您來說是一個問題,我認爲您需要使用Block與局部變量。

+0

非常好,非常感謝!我知道遞歸必須發揮作用,它只是逃離我的把握! –