Để làm điều đó, bạn sẽ phải xây dựng lại toàn bộ cây biểu thức; các tham số sẽ cần ánh xạ lại và tất cả các thành viên truy cập hiện đang nói chuyện với các loại khác nhau sẽ cần phải được áp dụng lại. May mắn thay, rất nhiều điều này được thực hiện dễ dàng hơn bởi lớp ExpressionVisitor
; ví dụ (làm tất cả trong trường hợp tổng quát, không chỉ là việc sử dụng Func<T,bool>
vị ngữ):
class TypeConversionVisitor : ExpressionVisitor
{
private readonly Dictionary<Expression, Expression> parameterMap;
public TypeConversionVisitor(
Dictionary<Expression, Expression> parameterMap)
{
this.parameterMap = parameterMap;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// re-map the parameter
Expression found;
if(!parameterMap.TryGetValue(node, out found))
found = base.VisitParameter(node);
return found;
}
protected override Expression VisitMember(MemberExpression node)
{
// re-perform any member-binding
var expr = Visit(node.Expression);
if (expr.Type != node.Type)
{
MemberInfo newMember = expr.Type.GetMember(node.Member.Name)
.Single();
return Expression.MakeMemberAccess(expr, newMember);
}
return base.VisitMember(node);
}
}
Ở đây, chúng tôi vượt qua trong một cuốn từ điển các thông số để tái bản đồ, áp dụng trong VisitParameter
. Chúng tôi cũng có, trong VisitMember
, hãy kiểm tra xem chúng tôi đã chuyển loại (có thể xảy ra nếu Visit
liên quan đến một số ParameterExpression
hoặc khác MemberExpression
, tại bất kỳ thời điểm nào): nếu có, chúng tôi sẽ thử và tìm một thành viên khác có cùng tên.
Tiếp theo, chúng ta cần một phương pháp mục đích chung lambda-chuyển đổi Ổ ghi:
// allows extension to other signatures later...
private static Expression<TTo> ConvertImpl<TFrom, TTo>(Expression<TFrom> from)
where TFrom : class
where TTo : class
{
// figure out which types are different in the function-signature
var fromTypes = from.Type.GetGenericArguments();
var toTypes = typeof(TTo).GetGenericArguments();
if (fromTypes.Length != toTypes.Length)
throw new NotSupportedException(
"Incompatible lambda function-type signatures");
Dictionary<Type, Type> typeMap = new Dictionary<Type,Type>();
for (int i = 0; i < fromTypes.Length; i++)
{
if (fromTypes[i] != toTypes[i])
typeMap[fromTypes[i]] = toTypes[i];
}
// re-map all parameters that involve different types
Dictionary<Expression, Expression> parameterMap
= new Dictionary<Expression, Expression>();
ParameterExpression[] newParams =
new ParameterExpression[from.Parameters.Count];
for (int i = 0; i < newParams.Length; i++)
{
Type newType;
if(typeMap.TryGetValue(from.Parameters[i].Type, out newType))
{
parameterMap[from.Parameters[i]] = newParams[i] =
Expression.Parameter(newType, from.Parameters[i].Name);
}
else
{
newParams[i] = from.Parameters[i];
}
}
// rebuild the lambda
var body = new TypeConversionVisitor(parameterMap).Visit(from.Body);
return Expression.Lambda<TTo>(body, newParams);
}
này có một tùy Expression<TFrom>
, và một TTo
, chuyển đổi nó vào một Expression<TTo>
, bởi:
- Phát hiện mà các loại khác nhau giữa
TFrom
/TTo
- sử dụng để sắp xếp lại các tham số
- sử dụng biểu thức-khách truy cập chúng tôi vừa tạo
- và cuối cùng là xây dựng một biểu thức lambda mới cho chữ ký mong muốn
Sau đó, đặt nó tất cả cùng nhau và phơi bày phương pháp mở rộng của chúng tôi:
public static class Helpers {
public static Expression<Func<TTo, bool>> Convert<TFrom, TTo>(
this Expression<Func<TFrom, bool>> from)
{
return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from);
}
// insert from above: ConvertImpl
// insert from above: TypeConversionVisitor
}
et thì đấy; một mục đích chung thường xuyên chuyển đổi lambda, với một thực hiện cụ thể của:
Expression<Func<Test, bool>> fc2 = fc1.Convert<TestDTO, Test>();
+1 cho wizardry sâu ... Đặt trên wizard mũ! –
@Marc Gravell: Bạn không chấp nhận câu trả lời này? –
@Kin ta: Tôi quên quay trở lại với nó - có sự chậm trễ khi chấp nhận câu trả lời tự –