【问题标题】:pass expressions and lambdas parameter to C# extension method in F#将表达式和 lambdas 参数传递给 F# 中的 C# 扩展方法
【发布时间】:2017-08-26 15:08:01
【问题描述】:

我正在努力解决这个问题,因为我只是 F# 的初学者,目前我正在学习 F#,方法是按照 F# 上的建议为 F# 中的 C# 代码创建单元测试,以获取乐趣和利润。

问题。

我有以下 C# 类

public class ObjectMapper<TSource, TTarget> 
    where TTarget : class, new() {

    public readonly TSource Source;
    public readonly TTarget Target;

    public ObjectMapper(TSource source) {

        this.Source = source;
        this.Target = new TTarget();
    }

    public ObjectMapper<TSource, TTarget> Populate<T>(
        Expression<Func<TTarget, T>> targetAccessor,
        Func<TSource, T> sourceValue) {

        var targetPropertyInfo = targetAccessor.ToPropertyInfo();
        targetPropertyInfo.SetValue(this.Target, sourceValue(this.Source));

        return this;
    }
}

我开始尝试在 F# 中编写一个简单的单元测试

module CompanyName.Utils.Test.ObjectMapper
open System
open Xunit
open Swensen.Unquote
open Microsoft.FSharp.Linq.RuntimeHelpers // do I need this?

open CompanyName.Utils // the namespace of the C# class above

type Source(name: string, code: int, date: DateTime) = 
    member thi.Name = name
    member this.Code = code
    member this.Date = date

type Target() = 
    member this.Id: string = System.String.Empty
    member this.Name: string = System.String.Empty
    member this.Code: int = 0
    member this.Date: DateTime = System.DateTime.MinValue

  [<Fact>]
  let ``ObjectMapper.Populate maps property values from source to target``() = 

   // arrange 
   let name = @"name"
   let code = 123
   let date = new System.DateTime(1980,2,15)
   let source = Source(name, code, date)
   // let target = Target()  
   let mapper = ObjectMapper<Source, Target>(source)  

   // act // this is how I would like it to be!
   mapper.Populate(t => t.Name, s => s.Name)
   mapper.Populate(t => t.Code, s => s.Code)
   mapper.Populate(t => t.Date, s => s.Date)
   mapper.Populate(t => t.Id, s => s.Date+s.Code+s.Name)

  // assert
  test<@ mapper.Target.Name = source.Name @>
  Assert.Equal(mapper.Target.Name, source.Name)

  test<@ mapper.Target.Code = source.Code @>
  Assert.Equal(mapper.Target.Code, source.Code)

  test<@ mapper.Target.Date = source.Date @>
  Assert.Equal(mapper.Target.Date, source.Date)

  test<@ mapper.Target.Id = @"1980-2-15123name" @>
  Assert.Equal(mapper.Target.Id, @"1980-2-15123name")

  Assert.True(false)

我的问题在于测试的断言部分,理想情况下我希望能够按照我在上面代码中使用的方式通过 lambda。不幸的是,这根本不起作用,我试图参考关于堆栈溢出的其他帖子,其中讨论了这个和相关主题。一般来说,这些帖子的级别对我来说有点太高了,虽然我理解一般概念,但我无法真正掌握必要的细节来将上面的元代码翻译成在 F# 中有意义的东西,最重要的是编译和运行.

如果你能帮助我,我将不胜感激

  1. 上面测试的断言部分,通过显示应该如何完成以及为什么元代码不能工作。
  2. 通过给出适合 5 岁儿童的示例,尝试了解 lambda 和表达式如何在 F# 和 C# 之间双向工作。
  3. 我可以用来更好地理解该主题的任何资源 可能以他们的难度增加的方式排序。

请随意对我的代码进行任何您认为合适的 cmets,以帮助我成为 F# 和 C# 中更好的开发人员,我一点也不介意。我知道上面的代码非常简单,有些部分是多余的和低效的。我想保持基本,以免混淆中心问题。

对于那些可能熟悉一种风格但不熟悉另一种风格的人来说,这些断言以 Unquote 和 MSTest 风格重复。

非常感谢您的帮助。

感谢 Fyodor Soiki 的解释,您可以在下面找到工作代码

module CompanyName.FSharp.UtilsFunctions

// Efficient concatenation of objects into a string with string builder
// https://stackoverflow.com/questions/18595597/is-using-a-stringbuilder-a-
right-thing-to-do-in-f
let strconc = 
    fun (data) ->         
        let sb = new System.Text.StringBuilder()
        for o in data do sb.Append(o.ToString()) |> ignore
        sb.ToString()

 // examples
 strconc ["one"; "two"] |> printfn "%s"
 strconc [1;2;3] |> printfn "%s"
 strconc (["one"; "two"; 3 ]: list<obj>) |> printfn "%s"

module LogXtreme.Utils.Test.ObjectMapper

open System
open Xunit
open Swensen.Unquote

open CompanyName.Utils
open CompanyName.FSharp.UtilsFunctions


type Source(name: string, code: int, date: DateTime) = 
    member thi.Name = name
    member this.Code = code
    member this.Date = date

type Target() = 
    member val Id: string = System.String.Empty with get, set 
    member val Name: string = System.String.Empty with get, set
    member val Code: int = 0 with get, set
    member val Date: DateTime = System.DateTime.MinValue with get, set

 [<Fact>]
 let ``ObjectMapper.Populate maps property values from source to target``() = 

     // arrange 
     let name = @"name"
     let code = 123
     let date = new System.DateTime(1980,2,15)    
     let source = Source(name, code, date)
     let mapper = ObjectMapper<Source, Target>(source)  
     let expectedId = strconc ([source.Name; source.Code; source.Date]: list<obj>) 

     // act     
     mapper.Populate((fun t -> t.Name), fun s -> s.Name) |> ignore
     mapper.Populate((fun t -> t.Code), fun s -> s.Code) |> ignore
     mapper.Populate((fun t -> t.Date), fun s -> s.Date) |> ignore    
     mapper.Populate((fun t -> t.Id), fun s -> strconc ([s.Name; s.Code; s.Date]: list<obj>)) |> ignore

     // assert
     test<@ mapper.Target.Name = source.Name @>    
     test<@ mapper.Target.Code = source.Code @>    
     test<@ mapper.Target.Date = source.Date @>
     test<@ mapper.Target.Id = expectedId @>

现在测试通过了。

【问题讨论】:

    标签: c# lambda f# expression


    【解决方案1】:

    通常,F# 倾向于避免神奇的转换,因为它们通常会导致微妙的、难以发现的错误。但是,在少数特殊情况下,主要是为了支持 C# 互操作场景,就会妥协。

    特别是,当调用需要 Expression&lt;_&gt; 的对象方法时,编译器会将 F# lambda 表达式转换为 C# 引号(与 C# 编译器相同)。这意味着您可以将普通的 F# lambdas 传递给您的方法:

    mapper.Populate( (fun t -> t.Name), fun s -> s.Name )
    

    第一个 lambda 周围的括号是必需的。没有它们,周围的一切都被解释为fun t -&gt; ( t.Name, fun s -&gt; s.Name )

    【讨论】:

    • 感谢 Fyodor 提供清晰简洁的解释和示例,并指出“引用”是一个值得研究的话题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-27
    相关资源
    最近更新 更多