将表达式和lambdas参数传递给F#中的C#扩展方法

问题描述:

由于我只是F#的初学者,而且目前我正在通过为F#中的C#代码创建单元测试来学习F#,因此我正在努力解决这个问题在F#上为了好玩和为了利益。将表达式和lambdas参数传递给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. 尝试了解lambdas和表达式在F#和C#之间如何在两个方向上工作,或者通过给出适合5岁的示例来实现。
  3. 任何资源,我可以用来获得更好的理解主题 可能订购的方式,他们在困难上升。

请随时对我的代码发表任何评论,以帮助我成为F#和C#中更好的开发人员,我完全不介意。我知道上面的代码非常简单,有些部分冗余且效率低下。我想保持它的基本,以避免中心问题。

这些断言在Unquote和MSTest样式中重复用于那些可能熟悉一种风格但不熟悉其他风格的人。

非常感谢您的帮助。

下面你找到工作代码拜陀Soiki解释

module CompanyName.FSharp.UtilsFunctions 

// Efficient concatenation of objects into a string with string builder 
// https://*.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 @> 

的测试通过。

一般来说,F#往往会避免魔法转换,因为它们通常会导致细微的,很难找到的错误。但是,在一些特殊情况下,主要是为了支持C#interop场景,它会妥协。

特别是,当调用期望Expression<_>的对象方法时,编译器会将F#lambda表达式转换为C#语句(与C#编译器的方式相同)。这意味着您可以将普通的F#lambda表达式传递给您的方法:

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

围绕第一个lambda表达式的括号是必需的。没有他们,整个事情被解释为fun t -> (t.Name, fun s -> s.Name)

+0

感谢Fyodor为您清晰简洁的解释和示例,并指出“引用”是一个要研究的主题。 – D76X