簡體   English   中英

將表達式和lambdas參數傳遞給F#中的C#擴展方法

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

我正在努力解決這個問題,因為我只是F#的初學者,目前我正在學習F#,通過在F#中為C#代碼創建單元測試,如F#所示,以獲得樂趣和利潤。

問題。

我有以下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. 我可以用來獲得更好地理解主題的任何資源,可能以他們遇到困難的方式訂購。

請隨意對我認為適合幫助我成為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 @>

測試現在通過。

一般來說,F#往往會避免神奇的轉換,因為它們通常會導致細微的,難以發現的錯誤。 但是,在一些特殊情況下,主要是為了支持C#互操作場景,它會妥協。

特別是,當調用期望Expression<_>的對象方法時,編譯器會將F#lambda表達式轉換為C#引用(與C#編譯器的方式相同)。 這意味着你可以將普通的F#lambdas傳遞給你的方法:

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

第一個lambda周圍的括號是必要的。 沒有它們,整個事物被解釋為fun t -> ( t.Name, fun s -> s.Name )

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM