从 F# 引用转换为 Linq 表达式时出现 InvalidOperationException
InvalidOperationException on conversion from F# quotation to Linq Expression
我正在尝试替换 F# Expr
中的类型,然后将其转换为 Expression
以供 c# 库使用。
但是在调用 LeafExpressionConverter.QuotationToExpression
时我收到错误
InvalidOperationException: The variable 't' was not found in the translation context
基本上,我正在尝试替换
的等价物
<@ fun (t: Record) -> t.A = 10 @>
至
<@ fun (t: Dict) -> t["A"] = 10 @>
这是代码
type Record = {
A: int
}
type Dict () = //this is the type the c# lib wants (a dictionary representation of a type)
inherit Dictionary<string, obj>()
let substitute<'a> (ex: Expr<'a->bool>) =
let replaceVar (v: Var) = if v.Type = typeof<'a> then Var(v.Name, typeof<Dict>) else v
let tEntityItem = typeof<Dict>.GetProperty("Item")
let isATypeShapeVar = function | ShapeVar var -> var.Type = typeof<'a> | _ -> false
let rec substituteExpr =
function
| PropertyGet(exOpt, propOrValInfo, c) ->
match exOpt with
| None -> Expr.PropertyGet(propOrValInfo)
| Some ex ->
let args = c |> List.map substituteExpr
let newex = substituteExpr ex
match isATypeShapeVar ex with
| true ->
let getter = Expr.PropertyGet(newex, tEntityItem, [Expr.Value(propOrValInfo.Name)] )
Expr.Coerce(getter, propOrValInfo.PropertyType)
| false -> Expr.PropertyGet(newex, propOrValInfo, args)
| ShapeVar var -> Expr.Var (var |> replaceVar)
| ShapeLambda (var, expr) -> Expr.Lambda(var |> replaceVar, substituteExpr expr)
| ShapeCombination(shapeComboObject, exprList) ->
RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)
substituteExpr ex |> LeafExpressionConverter.QuotationToExpression
substitute<Record> (<@ fun t -> t.A = 10 @>)
我怀疑我在替换中漏掉了什么,但我不知道是什么。
替换 F# Expr
的 .ToString()
结果是
Lambda (t,
Call (None, op_Equality,
[Coerce (PropertyGet (Some (t), Item, [Value ("A")]), Int32),
Value (10)]))
这看起来是正确的。除了强制转换,相当于 <@ fun (t: Dict) -> t["A"] = 10 @>.ToString()
为什么 QuotationToExpression
失败了?
每次调用 replaceVar
时,您 return 都是 Var
的不同实例。因此,当您替换 lambda 参数时,它是 Var
的一个实例,稍后,当您替换 newex
时,它是 Var
.
的另一个实例
Lambda (t, Call (None, op_Equality, [Coerce (PropertyGet (Some (t), ... ))
^ ^
| |
---------------------------------------------------------
These are different `t`, unrelated, despite the same name
要做到这一点,您必须使它保持原样 t
。最愚蠢、最直接的方法是:
let substitute<'a> (ex: Expr<'a->bool>) =
let newArg = Var("arg", typeof<Dict>)
let replaceVar (v: Var) = if v.Type = typeof<'a> then newArg else v
...
这将使您的特定示例按预期工作,但它仍然不可靠,因为您不仅要替换特定的 lambda 参数,而且要替换相同类型的 any 变量.这意味着如果表达式恰好包含与参数类型相同的任何变量,您仍然会遇到同样的问题。例如,尝试转换为:
<@ fun t -> let z = { A = 15 } in z.A = 15 && t.A = 10 @>
你会得到类似的错误,但这次抱怨变量 z
。
更好的方法是随时维护变量替换图,在第一次遇到新变量时插入新变量,但在后续遇到时从图中获取它们。
另一种方法是专门找出 lambda 参数,然后只替换它,而不是比较变量类型。
但是还有下一层的怪异:您正在将任何 属性 访问器转换为索引器访问器,但在我上面的示例中,不应该这样转换 z.A
。所以你必须以某种方式识别 属性 访问的对象是否实际上是参数,这可能不是那么微不足道。
如果您愿意满足 t.A
的情况,而在 (if true then t else t).A
等更复杂的情况下失败,那么您可以只匹配 lambda 参数并传递任何其他表达式:
let substitute<'a> (ex: Expr<'a->bool>) =
let arg =
match ex with
| ShapeLambda (v, _) -> v
| _ -> failwith "This is not a lambda. Shouldn't happen."
let newArg = Var("arg", typeof<Dict>)
let replaceVar (v: Var) = if v = arg then newArg else v
let tEntityItem = typeof<Dict>.GetProperty("Item")
let isATypeShapeVar = function | ShapeVar var -> var.Type = typeof<'a> | _ -> false
let rec substituteExpr =
function
| PropertyGet(Some (ShapeVar a), propOrValInfo, c) when a = arg ->
let getter = Expr.PropertyGet(Expr.Var newArg, tEntityItem, [Expr.Value(propOrValInfo.Name)] )
Expr.Coerce(getter, propOrValInfo.PropertyType)
| ShapeVar var -> Expr.Var (var |> replaceVar)
| ShapeLambda (var, expr) -> Expr.Lambda(var |> replaceVar, substituteExpr expr)
| ShapeCombination(shapeComboObject, exprList) ->
RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)
| ex -> ex
substituteExpr ex |> LeafExpressionConverter.QuotationToExpression
> substituteExpr <@ fun t -> let z = { A = 15 } in z.A = 15 && t.A = 10 @>
val it: System.Linq.Expressions.Expression =
ToFSharpFunc(arg => z => ((z.A == 15) AndAlso (Convert(arg.get_Item("A"), Int32) == 10)).Invoke(new Record(15)))
我正在尝试替换 F# Expr
中的类型,然后将其转换为 Expression
以供 c# 库使用。
但是在调用 LeafExpressionConverter.QuotationToExpression
时我收到错误
InvalidOperationException: The variable 't' was not found in the translation context
基本上,我正在尝试替换
的等价物<@ fun (t: Record) -> t.A = 10 @>
至
<@ fun (t: Dict) -> t["A"] = 10 @>
这是代码
type Record = {
A: int
}
type Dict () = //this is the type the c# lib wants (a dictionary representation of a type)
inherit Dictionary<string, obj>()
let substitute<'a> (ex: Expr<'a->bool>) =
let replaceVar (v: Var) = if v.Type = typeof<'a> then Var(v.Name, typeof<Dict>) else v
let tEntityItem = typeof<Dict>.GetProperty("Item")
let isATypeShapeVar = function | ShapeVar var -> var.Type = typeof<'a> | _ -> false
let rec substituteExpr =
function
| PropertyGet(exOpt, propOrValInfo, c) ->
match exOpt with
| None -> Expr.PropertyGet(propOrValInfo)
| Some ex ->
let args = c |> List.map substituteExpr
let newex = substituteExpr ex
match isATypeShapeVar ex with
| true ->
let getter = Expr.PropertyGet(newex, tEntityItem, [Expr.Value(propOrValInfo.Name)] )
Expr.Coerce(getter, propOrValInfo.PropertyType)
| false -> Expr.PropertyGet(newex, propOrValInfo, args)
| ShapeVar var -> Expr.Var (var |> replaceVar)
| ShapeLambda (var, expr) -> Expr.Lambda(var |> replaceVar, substituteExpr expr)
| ShapeCombination(shapeComboObject, exprList) ->
RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)
substituteExpr ex |> LeafExpressionConverter.QuotationToExpression
substitute<Record> (<@ fun t -> t.A = 10 @>)
我怀疑我在替换中漏掉了什么,但我不知道是什么。
替换 F# Expr
的 .ToString()
结果是
Lambda (t, Call (None, op_Equality, [Coerce (PropertyGet (Some (t), Item, [Value ("A")]), Int32), Value (10)]))
这看起来是正确的。除了强制转换,相当于 <@ fun (t: Dict) -> t["A"] = 10 @>.ToString()
为什么 QuotationToExpression
失败了?
每次调用 replaceVar
时,您 return 都是 Var
的不同实例。因此,当您替换 lambda 参数时,它是 Var
的一个实例,稍后,当您替换 newex
时,它是 Var
.
Lambda (t, Call (None, op_Equality, [Coerce (PropertyGet (Some (t), ... ))
^ ^
| |
---------------------------------------------------------
These are different `t`, unrelated, despite the same name
要做到这一点,您必须使它保持原样 t
。最愚蠢、最直接的方法是:
let substitute<'a> (ex: Expr<'a->bool>) =
let newArg = Var("arg", typeof<Dict>)
let replaceVar (v: Var) = if v.Type = typeof<'a> then newArg else v
...
这将使您的特定示例按预期工作,但它仍然不可靠,因为您不仅要替换特定的 lambda 参数,而且要替换相同类型的 any 变量.这意味着如果表达式恰好包含与参数类型相同的任何变量,您仍然会遇到同样的问题。例如,尝试转换为:
<@ fun t -> let z = { A = 15 } in z.A = 15 && t.A = 10 @>
你会得到类似的错误,但这次抱怨变量 z
。
更好的方法是随时维护变量替换图,在第一次遇到新变量时插入新变量,但在后续遇到时从图中获取它们。
另一种方法是专门找出 lambda 参数,然后只替换它,而不是比较变量类型。
但是还有下一层的怪异:您正在将任何 属性 访问器转换为索引器访问器,但在我上面的示例中,不应该这样转换 z.A
。所以你必须以某种方式识别 属性 访问的对象是否实际上是参数,这可能不是那么微不足道。
如果您愿意满足 t.A
的情况,而在 (if true then t else t).A
等更复杂的情况下失败,那么您可以只匹配 lambda 参数并传递任何其他表达式:
let substitute<'a> (ex: Expr<'a->bool>) =
let arg =
match ex with
| ShapeLambda (v, _) -> v
| _ -> failwith "This is not a lambda. Shouldn't happen."
let newArg = Var("arg", typeof<Dict>)
let replaceVar (v: Var) = if v = arg then newArg else v
let tEntityItem = typeof<Dict>.GetProperty("Item")
let isATypeShapeVar = function | ShapeVar var -> var.Type = typeof<'a> | _ -> false
let rec substituteExpr =
function
| PropertyGet(Some (ShapeVar a), propOrValInfo, c) when a = arg ->
let getter = Expr.PropertyGet(Expr.Var newArg, tEntityItem, [Expr.Value(propOrValInfo.Name)] )
Expr.Coerce(getter, propOrValInfo.PropertyType)
| ShapeVar var -> Expr.Var (var |> replaceVar)
| ShapeLambda (var, expr) -> Expr.Lambda(var |> replaceVar, substituteExpr expr)
| ShapeCombination(shapeComboObject, exprList) ->
RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)
| ex -> ex
substituteExpr ex |> LeafExpressionConverter.QuotationToExpression
> substituteExpr <@ fun t -> let z = { A = 15 } in z.A = 15 && t.A = 10 @>
val it: System.Linq.Expressions.Expression =
ToFSharpFunc(arg => z => ((z.A == 15) AndAlso (Convert(arg.get_Item("A"), Int32) == 10)).Invoke(new Record(15)))