5

There have been many questions around support for non-nullable reference types in .NET. The great hope was code contracts, but it is limited to runtime checking for those who have limited budget.

As for approaches other than Code Contracts, Jon Skeet wrote a blog post about this a few years ago, and one of the commenters provided a useful looking NonNull struct that had the IL modified to disable the default constructor. This seems like an excellent approach, and I can imagine extending it to provide all sorts of non-nullable microtypes. The IL manipulation could be a post-build step triggered by an attribute on the struct e.g.

//Microtype representing a non-zero age, so we want to disable the default ctor
[NoDefaultConstructor]
public struct Age
{
    public Age(int age)
    {
        // Implementation (including validation) elided
    }
} 

Before I investigate this further it I'd like to ask if anyone knows of any problems this might cause? I haven't been able to think of any.

Akash
  • 2,311
  • 1
  • 20
  • 37
  • What's your use case for needing a non-null reference type? – Anthony Pegram Oct 10 '11 at 13:50
  • 1
    For those whose budget stretches to ReSharper, there's some useful nullity-checking functionality in there (though obviously not as complete as the Contracts stuff in the pricey editions of VS) – AakashM Oct 10 '11 at 14:07
  • @AnthonyPegram I think most usages of reference types are implicitly non-null, so enforcing that via the method signature is a win in terms of documentation and safety. – Akash Oct 10 '11 at 14:34
  • @AakashM Thanks, I use ReSharper but haven't investigated its nullity checking. Will look into it! – Akash Oct 10 '11 at 14:35
  • Hi Akash, as you have seen in my update I completely agree with Ani that the default constructor hack is not viable. You say it yourself in the question "This seems like an excellent approach", but it really isn't due to the limitations Ani has mentioned in his answer, and I also agree that these cases aren't "rare". However, I do agree with you that runtime null-checking is a bit too late, but I don't see any way to support design/build time checking other than a change to the language/library/ide... Aka. Code contracts. But it is sad that this feature only comes in the expensive vs versions. – JohannesH Oct 11 '11 at 10:49
  • Also see [how-can-i-get-close-to-non-nullable-types-in-c-sharp-today](http://stackoverflow.com/questions/2181846/how-can-i-get-close-to-non-nullable-types-in-c-sharp-today) – nawfal Jul 08 '14 at 11:23

1 Answers1

6

This can be defeated quite easily - the run-time doesn't try to call the parameterless constructor of a struct (if present) in every scenario.

In particular, it won't get called when creating an array of the struct-type.

Age[] ages = new Age[3];

// This guy skips your "real" ctor as well as the "invalid" parameterless one.
Age age = ages[0];

...or in default(structType) expressions:

// Invalid state here too.
Age age = default(Age);

From Jon Skeet's empirical research into this stuff, here's a list of other operations that don't call the constructor:

  • Just declaring a variable, whether local, static or instance
  • Boxing
  • Using default(T) in a generic method
  • Using new T() in a generic method

Now the situation that leaves you in is that you have to somehow test for every Age instance whether or not the instance was created by working around your fence - which isn't much better than not having erected the fence in the first place.

svick
  • 236,525
  • 50
  • 385
  • 514
Ani
  • 111,048
  • 26
  • 262
  • 307
  • Thanks Ani, I'd missed Jon's follow up article. Whilst these are all valid concerns, I'm still left wondering if the approach has merit, especially for internal code e.g. NonNull in a method signature is clear documentation (with *some* compiler enforcement) as to the allowable values for the argument. I'm comfortable with an imperfect solution if the imperfections are only exposed via malicious users (my colleagues are professionals) or by rare accidents. However, I've not decided whether your examples fall into the "rare accident" category! – Akash Oct 10 '11 at 15:17
  • @Akash: I would hardly classify an array of structs or `default(T)` in a generic method as likely to be "malicious" / a "rare accident". Once we accept that, it is clear that we're going to have to validate every instance - which leaves us right back where started. :) – Ani Oct 10 '11 at 15:23
  • But if we are limiting discussion to microtypes, I can't see why anyone would instantiate it via a generic factory method (GetMicroType). Equally, rather than passing Age[] into a method, you would pass in an AgeCollection, which itself would have validation on its ctor args. I'm struggling to see what I'm missing, but given that I don't know of anyone using this approach, I feel that there must be practical issues rather than simply theoretical concerns. – Akash Oct 10 '11 at 18:50
  • The proper thing to have done with `String`, given that the COM framework with which .net was supposed to interact represented empty strings as null, would have been to specify that compilers must use a non-virtual call when invoking methods/properties on `String`, and have its methods regard `null` as an empty string. This would likely have required that `Nullable` not constrain `T` to struct, but losing that constraint would be a good thing anyway. – supercat May 15 '12 at 20:49