Preserving Field names across the F#/C# boundary

2019-06-26 06:07发布

问题:

As a learning exercise, I am doing some data analysis on a 5000-line csv file using F# and F#'s type-provider functionality. When I use the type-provider-generated type in f#, naturally the header rows of the csv file become the field names of the type.

type restaurantCsv = CsvProvider<"C:\Users\Paul\Desktop\HealthDepartment\RestaurantRatings2013.csv",HasHeaders=true>

type RawInspectionData(filename : string) = 
    member this.allData = restaurantCsv.Load(filename)

    member public this.inspectionsList = this.allData.Data.ToList()

    member this.FilterByName(name : string) =
        this.allData.Data 
            |> Seq.filter (fun x -> x.EstablishmentName.Contains(name))

Also, I am calling the F# code (in a library the above code creates) from a unit testing C# file. It works fine. But when I consume the IEnumberable that F# returns from FilterByName, the field name information is not preserved. So I have code which maps new field names to .Item1, Item2, etc. from the Tuple:

var inspectionsOnCafes = inspections2013.FilterByName("Cafe");
var inspections = (from row in inspectionsOnCafes
                    select new
                    {
                       inspectorID = row.Item3,
                       restaurantName = row.Item5,
                       inspectionDate = row.Rest.Item6
                    }).ToList();

My Question Is there a way to get C# to recognize the field names in the F#-generated type so I don't have to map row.Item# to an easy-to-read field name? In other words, is there a way I can have code like this:

inspectorID = row.InspectorID,
// etc.

Note: I looked at How do I create an F# Type Provider that can be used from C#? but Cameron Taggert's question and Tomas Petricek's answer seem to me to be on a slightly different aspect of type providers. Even if they are spot-on, I don't get it so I still need a little more help.

回答1:

In F# there are two distinct kinds of type providers. The more common and simpler ones are erased type providers. And the more complicated ones are generative type providers.

The distinction is very important for C#, because:

  • An erased type provider is just compiler magic. It is a way to make sure your code is well-formed, that you are using the underlaying API correctly. But this is compile-time only and no types are generated. Instead the underlaying type is used as an internal representation. This kind of type provider is not very convenient to use from C#, because in C# you only get the underlaying types.

  • A generative type provider is a true code generation step at compile-time. In this case types are generated and therefore actually exist in the assembly and can be used from C#.

The problem you are describing would be exactly what one would expect from an erased type provider. Whereas the underlaying type is just a tuple, but no nice type like a class or a record is generated. And you cannot use the F# names in C#, unless the type provider were to be written differently as a generative type provider.