- Feature Articles
- CodeSOD
- Error'd
- Forums
-
Other Articles
- Random Article
- Other Series
- Alex's Soapbox
- Announcements
- Best of…
- Best of Email
- Best of the Sidebar
- Bring Your Own Code
- Coded Smorgasbord
- Mandatory Fun Day
- Off Topic
- Representative Line
- News Roundup
- Editor's Soapbox
- Software on the Rocks
- Souvenir Potpourri
- Sponsor Post
- Tales from the Interview
- The Daily WTF: Live
- Virtudyne
Admin
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.
Admin
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.
Admin
Lemma. Everything can be abused.
Corollary 1. There always is a bad way.
Corollary 2. Somewhere, someone is doing it the bad way.
Admin
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.
Admin
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.
Admin
It's because DateTime? is actually Nullable{DateTime}, so it has a constructor should you so desire.
Admin
That's what makes it bad C#.
Admin
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?()
Admin
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.
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.
Admin
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.
Admin
Yeah, that's literally the last article with the IsNullOrEmpty function (https://thedailywtf.com/articles/isemptyornullornullorempty). Bad choice.
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:
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 ;-)
Admin
Man, you spell out the term GC and you get instantly held for moderation lol
Admin
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?)
Admin
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".
Admin
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.
Admin
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 thatnew DateTime?()
is actually compiled todefault
, 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.Admin
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
Admin
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.
Admin
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.
Admin
Count me amongst the C# programmers that had no idea you could even do this.
I certainly can't see why you would.
Admin
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.)
Admin
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.
Admin
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.
Admin
That is what MS themselves called the struct default constructor (as I mention, it's using the initobj valuetype IL code).
You still can't create a default constructor, but you can do this:
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).
Admin
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 callingnew S()
was the way to dodefault(S)
.Admin
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!).
Admin
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.Admin
Also:
default(T)
always doesinitobj
, regardless of which constructors are present, in every version of C# since 2 (whendefault
was added to the language).