简体   繁体   English

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

[英]pass expressions and lambdas parameter to C# extension method in F#

I am struggling with this one as I am just a beginner in F# and at present I am in the process of learning F# by creating unit tests for C# code in F# as suggested on F# for fun and for profit. 我正在努力解决这个问题,因为我只是F#的初学者,目前我正在学习F#,通过在F#中为C#代码创建单元测试,如F#所示,以获得乐趣和利润。

The problem. 问题。

I have the following C# class 我有以下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;
    }
}

and I started out with trying to write a simple unit test in F# 我开始尝试在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)

My problem is with the assert part of the test where ideally I would have liked to be able to just pass the lambdas the way I have used in the code above. 我的问题在于测试的断言部分,理想情况下,我希望能够像上面代码中使用的那样传递lambda。 Unfortunately, that does not work at all and I tried to refer to other post on stack overflow where this and related topics are discussed. 不幸的是,这根本不起作用,我试图引用其他关于堆栈溢出的帖子,其中讨论了这个和相关的主题。 In general the level of these posts is a bit too high for me and although I understand the general concepts I fail to really grasp the necessary details to translate the meta-code above in something that makes any sense in F# and above all compiles and runs. 总的来说,这些帖子的级别对我来说有点太高了,虽然我理解了一般概念但是我没有真正掌握必要的细节来将上面的元代码转换为在F#中有意义的东西,最重要的是编译和运行。

I would be very grateful if you could help me with 如果你能帮助我,我将非常感激

  1. The assert part of the test above by showing how it should be done and why the metacode cannot work. 上面测试的断言部分通过展示它应该如何完成以及为什么元代码无法工作。
  2. Trying to understand how lambdas and expressions work between F# and C# in both directions either by giving examples suitable for a 5-year-old. 尝试通过给出适合5岁的例子来理解lambda和表达式如何在两个方向上在F#和C#之间起作用。
  3. Any resources that I could use to gain a better understanding of the subject possibly ordered in a way that they ramp up in difficulty. 我可以用来获得更好地理解主题的任何资源,可能以他们遇到困难的方式订购。

Please, feel free to make any comments to my code that you see fit to help me to become a better developer in F# as well as in C#, I do not mind at all. 请随意对我认为适合帮助我成为F#以及C#中更好的开发人员的代码发表任何评论,我根本不介意。 I am aware that the code above is very simple and some parts are redundant and inefficient. 我知道上面的代码非常简单,有些部分冗余且效率低下。 I thought to keep it basic in order not to cloud the central problem. 我认为要保持基本,以免影响核心问题。

The assertions are repeated in Unquote and MSTest styles for those who may be familiar with one style but not the other. 对于那些可能熟悉一种风格但不熟悉另一种风格的人,可以在Unquote和MSTest样式中重复这些断言。

Thank you very much for you help. 非常感谢你的帮助。

Below you find the working code thanks to Fyodor Soiki explanation 下面是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 @>

The test now passes. 测试现在通过。

In general, F# tends to avoid magical conversions, because they usually cause subtle, hard to find bugs. 一般来说,F#往往会避免神奇的转换,因为它们通常会导致细微的,难以发现的错误。 However, in a few special circumstances, mostly to support C# interop scenarios, it will compromise. 但是,在一些特殊情况下,主要是为了支持C#互操作场景,它会妥协。

In particular, when calling an object method that expects an Expression<_> , the compiler will convert F# lambda expressions to C# quotations (the same way C# compiler does). 特别是,当调用期望Expression<_>的对象方法时,编译器会将F#lambda表达式转换为C#引用(与C#编译器的方式相同)。 This means that you can just pass plain F# lambdas to your methods: 这意味着你可以将普通的F#lambdas传递给你的方法:

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

The parentheses around the first lambda are necessary. 第一个lambda周围的括号是必要的。 Without them, the whole thing around be interpreted as fun t -> ( t.Name, fun s -> s.Name ) 没有它们,整个事物被解释为fun t -> ( t.Name, fun s -> s.Name )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM