• Jonathan (unregistered)

    I've used C# as my primary language for over a decade and I didn't even realise that was possible, I guess like Remy, the situation never arose where it seemed remotely useful and thus I never even contemplated if it could be done.

  • (nodebb)

    Same here, I never knew this was possible. I guess it proves the statement that if there's a bad way to do anything, there will be somebody somewhere who does it the bad way, regardless of how ridiculous it is.

  • Hanzito (unregistered) in reply to Mr. TA

    Lemma. Everything can be abused.

    Corollary 1. There always is a bad way.

    Corollary 2. Somewhere, someone is doing it the bad way.

  • NotAThingThatHappens (unregistered)

    Corollary 3. Somehow the bad way becomes the first hit on the search engine. Corollary 4. Cargo Cult Programming makes the bad way the default way.

  • John Melville (unregistered)

    This isn't even bad C# in my opinion.

    In C# DateTime is a value type, and nullable value types are instances of the Nullable<T> struct. While most people use syntactic sugar like DateTime? x = null; or DateTime? d = default, var x =new DateTime?() is equivalent to var x = new Nullable<DateTime>() which is the canonical form that all the other syntactic sugar compiles down to for creating a nullable DateTime with an initial value of null.

    Se, in my book, 10 points for knowing what the compiler is actually doing, and -1 for not knowing that there is syntactic sugar that will make this look more familiar for most programmers.

  • (nodebb)

    It's because DateTime? is actually Nullable{DateTime}, so it has a constructor should you so desire.

  • (nodebb) in reply to John Melville

    and -1 for not knowing that there is syntactic sugar that will make this look more familiar for most programmers.

    That's what makes it bad C#.

  • (nodebb) in reply to John Melville

    Agreed. I bet the original developer had:

    var x =new DateTime?()

    And somebody decided they didn't like var - I've seen plenty of coding "standards" saying not to use it...So then some other coder mechanically changed it to:

    DateTime? x = new DateTime?()

  • (nodebb)

    The worst part about this, is for a second I thought new DateTime?() would create a new DateTime struct*, not null, but rather zeroed out, and stuff that into a DateTime? object. So I would have thought this code is doing the opposite to what it actually does, which makes it so much worse than DateTime? current = null.

    • Looking it up, I see there is no parameterless constructor for DateTime.

    I've never seen this construct before, so I would have had to look it up to see what it actually does. Which is where it gets even worse yet again, as I'm not even sure how to look this up. It's not obvious from the API documentation for the Nullable<T> constructor what happens when there is no value provided. Google searches are tricky, because it's not like there are many examples of people doing this.

  • (nodebb)

    The addition of nullable or optional types to mainstream languages was a net good. It doesn't completely solve the billion dollar mistake, but it makes it far easier to write safe code.

    It actually 100% solves the issue. When it is still there, then some developer actively ignored it. There are certain ways to cheat yourself out of the commitment like the bang operator, but beyond a very few legit reasons (do to historic class library limitations), it's pretty much impossible to fail proper null handling.

    However this example has the real billion dollar trap: People not using UTC. That's where the real money is and it is often ignored because it's a way harder problem to understand than a simple null check.

  • (nodebb)

    DateTime? current = someFunctionWhichMayReturnAValueOrNull();

    Yeah, that's literally the last article with the IsNullOrEmpty function (https://thedailywtf.com/articles/isemptyornullornullorempty). Bad choice.

    DateTime? current = new DateTime?();

    This line means you create a nullable value type of DateTime. It's not syntactical sugar, it's literally a new Nullable<T> struct. Now there's a lot of syntactical sugar in C# to make the type seemingly behave like a reference type (magic null comparison operator) but it has nothing to do with a reference type but is heavily simplified:

    public struct Nullable<T> where T : struct
    {
      public bool HasValue;
      public T Value;
    }
    

    That's one of the reasons why .net is about 2x-3x as fast as Java and about 5x-10x faster than Phyton in long running benchmark; it simply doesn't matter how efficient your GC is, now garbage collection is always faster ;-)

  • (nodebb)

    Man, you spell out the term GC and you get instantly held for moderation lol

  • (nodebb)

    The site claims the "billion dollar mistake" speech happened in 2009. Given the increasing size of the programming industry, has it hit a trillion dollars yet? (...even if it's just on the very upper end of the estimate?)

  • (nodebb) in reply to Mr. TA

    As I wrote already in my held for moderation type, DateTime? is a value type with a HasValue property. So the example is actually a property way to do it (or just shorten it to var x = new DateTime?() or DateTime? = new()). There is a syntactical sugar that allows you to do DateTime? x = null, but it translate directly to construction the struct.

    Compared to a lot other GC languages .net features value types, so you can create type safe stack objects. Stack objects must be initialized by convention (there's a workaround intended for unmanaged structs, so lets ignore that for now) and by default it's setting everything to zero. This means whatever the Value is and the boolean HasValue, if you use an empty constructor, they will be zero - aka HasValue == false, which is equal to the struct being "null".

  • Duke of New York (unregistered)

    Compile-time checked nullability is fine. Optional values without compiler support, rather silly. “What if we had a sentinel value that wasn’t called null?” Thanks a bunch, galaxy brain.

  • (nodebb) in reply to MaxiTB

    Actually, you're mistaken.

    https://github.com/microsoft/referencesource/blob/master/mscorlib/system/nullable.cs

    Nullable<T> doesn't have a default constructor. This means that new DateTime?()is actually compiled to default, not the other way around.

    Addendum 2024-08-29 14:07: 1, replace "default constructor" with "no-arg constructor". 2, to clarify, default is never compiled to call the no-arg constructor even if one already exists.

  • (nodebb) in reply to Mr. TA

    Structs have always a default constructor, the one setting all the members to zero. Hence you can't make a custom default constructor for structs, you have to make one with arguments ;-)

    Addendum 2024-08-29 15:40: The IL opcode for the struct default constructor is called initobj valuetype. So technically it's ofc not a constructor, it's just described as one on a language level (I didn't choose those names). So everything from initobj valuetype, default constructor or parameterless constructor all mean the same thing and translate to initobj. It's just less confusing to use the original naming for those not familiar with IL.

    https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.initobj?view=net-8.0

    https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct

  • (nodebb) in reply to MaxiTB

    No they don't. When a struct doesn't have a no-arg constructor, calling new S() emits a special IL opcode which zeroes the bytes, no actual constructor is invoked.

    And structs can have no-arg constructors as of a recent version (I don't remember which one), in which case new S() becomes a ctor call.

    No offense but your information is partially incorrect and partially outdated.

  • (nodebb)

    I did ask the C# team to deprecate new S() when there is no parameterless constructor, but they're extremely cautious about breaking changes, which is understandable.

    If they do it, the code in this WTF will become illegal.

  • Loren Pechtel (unregistered)

    Count me amongst the C# programmers that had no idea you could even do this.

    I certainly can't see why you would.

  • Stuart (unregistered)

    Off topic a little bit, but I remember working with an enterprise (spit) application construction toolkit back in the late 90s. It was objected oriented, and had types like TextData, IntegerData, DateTimeData, and so forth: objects that wrapped around the primitive types (in the case of TextData, IntegerData, and similar.)

    It also had types like TextNullable, IntegerNullable, DateTimeNullable, etc., etc. So far, so good.

    The *Nullable types were subclasses of the *Data types. Meaning that if your code expected a *Data type, it COULD receive a *Nullable type, even if you wanted a definitely-cannot-be-Null type. And if your code expected a *Nullable type, it couldn't receive the corresponding *Data type, despite a *Data type being perfectly acceptable in that context. They really screwed that up - it should have been *Data inherits from *Nullable (and disallows the null option as part of the code overrides.)

    But that system went end of life and completely out of support in April 2009, so nobody's using it any more, right? Right? (The intellectual property in question is now owned by Oracle, which is even more reason to not use it any more, as if it being fifteen years out of support isn't enough reason. It's the Forte 4GL, if anybody really cares about the exact package name.)

  • Tim (unregistered)

    Wow - this just brought home to me how long it is since I wrote code that mutates local variables.

    I was sitting here thinking "why would you want to define a value that's always null?" then I remembered that in C# the code might change the value of it afterwards.

  • TheCPUWizard (unregistered)

    Nullable<T> is a struct, therefore it is ALWAYS initialized (zero field) the default constructor (as shown) does the exact same thing as the implicit initializer (in the end, there are differences in the intermediate steps...) But it does facilitate the ability to detect cases where the value is not immediately initialized which can otherwise be harder to detect.

  • (nodebb) in reply to Mr. TA

    When a struct doesn't have a no-arg constructor, calling new S() emits a special IL opcode which zeroes the bytes, no actual constructor is invoked.

    That is what MS themselves called the struct default constructor (as I mention, it's using the initobj valuetype IL code).

    And structs can have no-arg constructors as of a recent version (I don't remember which one), in which case new S() becomes a ctor call.

    You still can't create a default constructor, but you can do this:

    public struct X
    {
      public X(int value = 0) { Value = value; }
      public int Value;
    }
    

    There was a bug in older versions where instead of calling the custom constructor with the optional parameter a simple initobj valuetype was used for default(T). This behavior has long been fixed and the custom constructor with the optional parameter will be called instead instead of the default constructor (aka initobj valuetype). There is no need to call the default constructor, because all structs are sealed and by standard declaration all members need to be initialized in the custom constructor (spoiler: the padding will be still zeroed and while the standard demands padding to be ignored some internal comparison implementations will actually do a full comparison including padding which can be an fun experience if you don't marshal unmanaged structs correctly).

  • (nodebb) in reply to MaxiTB

    https://sharplab.io/#v2:CYLg1APgAgTAjAWAFBQMwAIDOAXATgVwGNt0BhdAb2XRvTTIAoBKCgX2tvqgBZ0BZZpQ412SVkA=

    Addendum 2024-08-30 17:49: You are right, they do use that language of "every struct has a parameterless constructor". It's unfortunate that they insist on calling it that. I guess it's too much of a breaking change for them to stomach. It's a legacy of C# v1 where there was no default keyword and calling new S() was the way to do default(S).

  • Petr Kadlec (github)

    The "new X?()" syntax is useful for type inference. As mentioned above, the code might have originally used var (even though there I'd prefer "X? x = null"). But you can also use it in generic method calls where otherwise you'd need to write "(X?)null" which is more cumbersome, IMHO (the terrible casting syntax inherited from C!).

  • (nodebb)

    new DateTime?() is something I've used in ternaries, because if you write:

    DateTime? someDate = someCondition ? DateTime.Now : null

    the compiler whines that it can't infer the type of the ternary expression. Using new DateTime?() here fixes the ambiguity.

  • (nodebb)

    Also:

    There was a bug in older versions where instead of calling the custom constructor with the optional parameter a simple initobj valuetype was used for default(T).

    default(T) always does initobj, regardless of which constructors are present, in every version of C# since 2 (when default was added to the language).

Leave a comment on “Null Ability”

Log In or post as a guest

Replying to comment #:

« Return to Article