Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# / .NET null instance oddity

Tags:

c#

.net

f#

I have this C# DLL:

namespace TestCSProject
{
    public class TestClass
    {
        public static TestClass Instance = null;

        public int Add(int a, int b)
        {
            if (this == null)
                Console.WriteLine("this is null");
            return a + b;
        }
    }
}

And this F# app which references the DLL:

open TestCSProject
printfn "%d" (TestClass.Instance.Add(10,20))

No-one initiates the Instance static variable. Guess what the output of the F# app is?

this is null
30
Press any key to continue . . .

After a few tests I found out that unless I use this (e.g. to access instance field), I won't get NullReferenceExpcetion.

Is that an intended behaviour or a gap in F# compilation / CLR?

like image 526
Elephantik Avatar asked Sep 20 '10 15:09

Elephantik


2 Answers

I suspect you'll find it's using call instead of callvirt if you look at the IL. The C# compiler always uses callvirt, even for non-virtual methods, because it forces a nullity check.

Is this a bug? Well, not necessarily. It depends on what the F# language specification states about methods calls on null references. It's perfectly possible that it states that the method will be called (non-virtually) with a null "this" reference, which is exactly what has happened.

C# happens to specify that this sort of dereferencing will throw a NullReferenceException, but that's a language choice.

I suspect the F# approach may be a little faster, due to the lack of nullity checking involved... and don't forget that null references are less "expected" in F# than in C#... that may explain the different approach taken here. Or it could just be an oversight, of course.

EDIT: I'm not an expert at reading the F# specification, but section 6.9.6 at least suggests to me that it's a bug:

6.9.6 Evaluating Method Applications

For elaborated applications of methods, the elaborated form of the expression will be either expr.M(args) or M(args).

  • The (optional) expr and args are evaluated in left-to-right order and the body of the member is evaluated in an environment with formal parameters that are mapped to corresponding argument values.

  • If expr evaluates to null then NullReferenceException is raised.

  • If the method is a virtual dispatch slot (that is, a method that is declared abstract) then the body of the member is chosen according to the dispatch maps of the value of expr.

Whether this counts as an elaborated application or not is a little beyond me, I'm afraid... but I hope this has been at least somewhat helpful.

like image 193
Jon Skeet Avatar answered Nov 10 '22 15:11

Jon Skeet


I think that the F# team considers this behavior a bug that is likely to change in a future release.

Occasionally a class may change a method from non-virtual (in release N) to virtual (in release N+1). Is this a "breaking change"? It is breaking if code compiled against the original class used call rather than callvirt. The concept of "breaking change" is mostly a matter of degree rather than kind; the non-virtual to virtual change is a "minor" breaking change. In any case, some classes in the .NET Framework have exhibited this kind of change, and thus you run into a problem analogous to the "fragile base class". Witnessing this, the F# team intends to change the compiler codegen to use callvirt like C# and VB do. Assuming that happens, a few unlikely programs, such as the one in this repro, would change behavior when compiled with a future version of the F# compiler.

like image 3
Brian Avatar answered Nov 10 '22 13:11

Brian