Search code examples
f#upcastingidatareader

this expression was expected to have type IDataReader but here has type SqlDataReader


The following code is not casting my return value of SqlDataReader from getReader correctly to IDataReaderin the call to Seq.unfold. What am I doing wrong?

open System.Data
open System.Data.SqlClient
open System.Configuration

type Foo = { id:int; name:string }

let populateFoo (r:IDataReader) =
    let o = r.GetOrdinal
    { id = o "id" |> r.GetInt32; name = o "name" |> r.GetString; }

let iter populateObject (r:IDataReader)  =
    match r.Read() with
    | true -> Some(populateObject r, r)
    | _    -> None

let iterFoo = iter populateFoo

let getReader : IDataReader =
    let cnstr = ConfigurationManager.ConnectionStrings.["db"].ConnectionString
    let cn = new SqlConnection(cnstr)
    let cmd = new SqlCommand("select * from Foo", cn)
    cmd.ExecuteReader()

let foos = Seq.unfold iterFoo getReader

Solution

  • F# does not automatic upcasting like C#, except in some specific scenarios (see the spec, section 14.4.2).

    You have to explicitly cast the expression: cmd.ExecuteReader() :> IDataReader then you can remove the type annotation after getReader.

    Alternatively you may leave that function returning an SqlDataReader and upcast at the call site:

    let foos = getReader :> IDataReader |> Seq.unfold iterFoo
    

    If unfold was a static member of a type with a signature like this one:

    type T() =
        static member unfold(a, b:IDataReader) = Seq.unfold a b
    

    you would be able to do directly T.unfold(iterFoo, getReader) and it will automatically upcast. That's one of the cases mentioned in the spec.