如何使用通用转换器将 FileHelpers.engine 与 F# 选项类型一起使用?
How do you use FileHelpers.engine with F# option types using generic converters?
展示了将 CsvHelper 与通用 F# 选项类型一起使用的好方法。您如何使用 FileHelpers 做同样的事情?
我不知道如何使通用 OptionConverter 工作。考虑以下 test.csv
:
X,Y,Z
hi,1,hi
bye,,
以下作品:
#r "nuget: FileHelpers"
open System
open FileHelpers
Environment.CurrentDirectory <- __SOURCE_DIRECTORY__
type OptionFloatConverter() =
inherit ConverterBase()
override this.FieldToString(x) =
match x :?> Option<float> with
| None -> ""
| Some x -> base.FieldToString(x)
override __.StringToField(x) =
match x with
| "" -> None :> obj
| s -> s |> float |> Some :> obj
type OptionStringConverter() =
inherit ConverterBase()
override this.FieldToString(x) =
match x :?> Option<string> with
| None -> ""
| Some x -> base.FieldToString(x)
override __.StringToField(x) =
match x with
| "" -> None :> obj
| s -> s |> Some :> obj
[<CLIMutable>]
[<DelimitedRecord(",")>]
[<IgnoreFirst(1)>]
type TestExplicit =
{ X : string;
[<FieldConverter(typeof<OptionFloatConverter>)>]
Y : float Option
[<FieldConverter(typeof<OptionStringConverter>)>]
Z : string Option
}
let generic = FileHelperEngine<TestGeneric>()
explicit.ReadFile "test.csv"
(*
val it : TestExplicit [] = [|{ X = "hi"
Y = Some 1.0
Z = Some "hi" }; { X = "bye"
Y = None
Z = None }|]
*)
如何使用这个通用版本?
type OptionConverter<'T>() =
inherit ConverterBase()
override this.FieldToString(x) =
match x :?> Option<'T> with
| None -> ""
| Some x -> base.FieldToString(x)
override __.StringToField(x) =
match x with
| "" -> None :> obj
| s -> Convert.ChangeType(s,typeof<'T>) |> Some :> obj
[<CLIMutable>]
[<DelimitedRecord(",")>]
[<IgnoreFirst(1)>]
type TestGeneric =
{ X : string;
[<FieldConverter(typeof<OptionConverter<float>>)>]
Y : float Option
[<FieldConverter(typeof<OptionConverter<string>>)>]
Z : string Option
}
let explicit = FileHelperEngine<TestExplicit>()
generic.ReadFile "test.csv"
(*
> generic.ReadFile "test.csv";;
FileHelpers.ConvertException: Line: 2. Field: Y@. The converter for the field: Y@ returns an object of Type: FSharpOption`1 and the field is of type: FSharpOption`1
---> System.InvalidCastException: Unable to cast object of type 'Microsoft.FSharp.Core.FSharpOption`1[System.Object]' to type 'Microsoft.FSharp.Core.FSharpOption`1[System.Double]'.
at lambda_method4(Closure , Object , Object[] )
at FileHelpers.RecordOperations.StringToRecord(Object record, LineInfo line, Object[] values)
--- End of inner exception stack trace ---
at FileHelpers.RecordOperations.StringToRecord(Object record, LineInfo line, Object[] values)
at FileHelpers.FileHelperEngine`1.ReadStreamAsList(TextReader reader, Int32 maxRecords, DataTable dt)
at FileHelpers.FileHelperEngine`1.ReadStream(TextReader reader, Int32 maxRecords)
at FileHelpers.FileHelperEngine`1.ReadFile(String fileName, Int32 maxRecords)
at FileHelpers.FileHelperEngine`1.ReadFile(String fileName)
at <StartupCode$FSI_0004>.$FSI_0004.main@()
Stopped due to error
*)
问题出在您的 StringToField
方法中。 Convert.ChangeType
返回的静态类型是 obj
,因此结果选项的类型是 Option<obj>
,而不是 Option<float>
。要解决此问题,请在调用 Some
:
之前向下转换转换后的值
override __.StringToField(x) =
match x with
| "" -> None :> obj
| s ->
let value = Convert.ChangeType(s,typeof<'T>) :?> 'T
Some value :> _
我不知道如何使通用 OptionConverter 工作。考虑以下 test.csv
:
X,Y,Z
hi,1,hi
bye,,
以下作品:
#r "nuget: FileHelpers"
open System
open FileHelpers
Environment.CurrentDirectory <- __SOURCE_DIRECTORY__
type OptionFloatConverter() =
inherit ConverterBase()
override this.FieldToString(x) =
match x :?> Option<float> with
| None -> ""
| Some x -> base.FieldToString(x)
override __.StringToField(x) =
match x with
| "" -> None :> obj
| s -> s |> float |> Some :> obj
type OptionStringConverter() =
inherit ConverterBase()
override this.FieldToString(x) =
match x :?> Option<string> with
| None -> ""
| Some x -> base.FieldToString(x)
override __.StringToField(x) =
match x with
| "" -> None :> obj
| s -> s |> Some :> obj
[<CLIMutable>]
[<DelimitedRecord(",")>]
[<IgnoreFirst(1)>]
type TestExplicit =
{ X : string;
[<FieldConverter(typeof<OptionFloatConverter>)>]
Y : float Option
[<FieldConverter(typeof<OptionStringConverter>)>]
Z : string Option
}
let generic = FileHelperEngine<TestGeneric>()
explicit.ReadFile "test.csv"
(*
val it : TestExplicit [] = [|{ X = "hi"
Y = Some 1.0
Z = Some "hi" }; { X = "bye"
Y = None
Z = None }|]
*)
如何使用这个通用版本?
type OptionConverter<'T>() =
inherit ConverterBase()
override this.FieldToString(x) =
match x :?> Option<'T> with
| None -> ""
| Some x -> base.FieldToString(x)
override __.StringToField(x) =
match x with
| "" -> None :> obj
| s -> Convert.ChangeType(s,typeof<'T>) |> Some :> obj
[<CLIMutable>]
[<DelimitedRecord(",")>]
[<IgnoreFirst(1)>]
type TestGeneric =
{ X : string;
[<FieldConverter(typeof<OptionConverter<float>>)>]
Y : float Option
[<FieldConverter(typeof<OptionConverter<string>>)>]
Z : string Option
}
let explicit = FileHelperEngine<TestExplicit>()
generic.ReadFile "test.csv"
(*
> generic.ReadFile "test.csv";;
FileHelpers.ConvertException: Line: 2. Field: Y@. The converter for the field: Y@ returns an object of Type: FSharpOption`1 and the field is of type: FSharpOption`1
---> System.InvalidCastException: Unable to cast object of type 'Microsoft.FSharp.Core.FSharpOption`1[System.Object]' to type 'Microsoft.FSharp.Core.FSharpOption`1[System.Double]'.
at lambda_method4(Closure , Object , Object[] )
at FileHelpers.RecordOperations.StringToRecord(Object record, LineInfo line, Object[] values)
--- End of inner exception stack trace ---
at FileHelpers.RecordOperations.StringToRecord(Object record, LineInfo line, Object[] values)
at FileHelpers.FileHelperEngine`1.ReadStreamAsList(TextReader reader, Int32 maxRecords, DataTable dt)
at FileHelpers.FileHelperEngine`1.ReadStream(TextReader reader, Int32 maxRecords)
at FileHelpers.FileHelperEngine`1.ReadFile(String fileName, Int32 maxRecords)
at FileHelpers.FileHelperEngine`1.ReadFile(String fileName)
at <StartupCode$FSI_0004>.$FSI_0004.main@()
Stopped due to error
*)
问题出在您的 StringToField
方法中。 Convert.ChangeType
返回的静态类型是 obj
,因此结果选项的类型是 Option<obj>
,而不是 Option<float>
。要解决此问题,请在调用 Some
:
override __.StringToField(x) =
match x with
| "" -> None :> obj
| s ->
let value = Convert.ChangeType(s,typeof<'T>) :?> 'T
Some value :> _