简体   繁体   中英

Inheritance null reference exception using F#

Is there any way to make the following code work?

open System.Collections.Generic

type Geometry<'t>(child: 't) =
  let values = List()
  member o.add (v: float) = values.Add v; child

and Line() as self =
  inherit Geometry<Line>(self)
  member o.length v = o.add v

let line = Line().length(50.0)

I get

System.NullReferenceException: Object reference not set to an instance of an object.

EDIT:

It is enough to call the following to trigger the exception.

let line = Line()

The motivation is that you can do eg:

let line = Line().x1(10).y1(20).x2(30).y2(10).color("blue") // ...

and you can reuse common members among all geometries (circle, ellipse, ...)

open System.Collections.Generic

type Geometry<'t when 't :> Geometry<'t>>() =
  let values = List()
  member o.add (v: float) = values.Add v; o :?> 't

and Line() =
  inherit Geometry<Line>()
  member o.length v = o.add v

let line = Line().length(50.0)

In .NET (and by extension F#) you need to initialize an object before you can call any method on it or pass it to another method. Thus, your Line object would need to be initialized before it is passed to the base constructor Geometry<Line>(self) . But it can't be initialized before the base constructor is called, so there's no direct way to do what you want.

Having said that, F# has a system that is supposed to catch illegal recursive uses of values before they're defined, so I'm surprised you don't get a more meaningful exception instead of a NullReferenceException (or better yet, a compile-time error). Compare, for example, what happens if you try to call new Rec() with the following definition:

type Rec(r:Rec) =
    new() as self = Rec(self)

One way around this problem is should be use laziness to delay the use of the self identifier:

type Geometry<'t>(child: Lazy<'t>) =
  let values = List()
  member o.add (v: float) = values.Add v; child.Value

and Line() as self =
  inherit Geometry<Line>(lazy self)
  member o.length v = o.add v

Sadly, this doesn't work, lending further credence to the theory that there's an F# bug with checking the initialization soundness in this case. Fortunately, this can be worked around by using an explicit constructor for Line instead of a primary constructor:

type Geometry<'t>(child: Lazy<'t>) =
  let values = List()
  member o.add (v: float) = values.Add v; child.Value

and Line =
  inherit Geometry<Line>
  new() as self = { inherit Geometry<Line>(lazy self) }
  member o.length v = o.add v

One thing to note about F# -- one of it's major features is that it's very concise (especially compared to C# or Java). In my experience, though, it's also easy to get carried away with trying to make your code as concise as possible; instead, you're better off aiming to write code which is as concise as practical .

The reason I bring this up is because of your use of the semicolon to put multiple statements on one line -- it makes the code shorter by saving one or two newlines, but the downside is that it doesn't play nicely with the VS debugger.

If you remove your uses of the semicolon, you should be able to place breakpoints on the lines in question, then step through them with the VS debugger (or MonoDevelop / Xamarin Studio). Knowing specifically which statement is causing the exception to be thrown makes it much easier to determine the cause of the problem.

If you try this and are able to identify the line in question, please add that information to your question and we'll be able to give you a better answer.

type Geometry<'t> (child: 't) =
  let values = ResizeArray ()
  member o.add (v: float) =
    values.Add v
    child

and Line () as self =
  inherit Geometry<Line> (self)
  member o.length v =
    o.add v

let line =
    let l = Line ()
    l.length 50.0

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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