Cute, but is this actually needed? It's one more thing to remember, one more thing to know the subtleties of, and for what? To save writing a very readable and unambiguous line of code?
It feels like the C# designers have a hard time saying "no" to ideas coming their way. It's one of my biggest annoyances with this otherwise nice language. At this point, C# has over 120 keywords (incl. contextual ones) [0]. This is almost twice as much as Java (68) [1], and 5 times as much as Go (25) [2]. And for what? We're trading brevity for complexity.
I stumbled over this a few times. This basically just means that using the ?. member access no longer dictates what is horrible on the right side. Because property reads were fine before (returning null if a part of the chain was null), method invocations were fine (either returning null or just being a no-op if a part of the chain was null). But assignments were not, despite syntactically every ?. is basically an if statement, preventing the right side from executing if the left side is null (yes, that includes side-effects from nested expressions, like arguments to invocations).
So this is not exactly a new feature, it just removes a gotcha from an old one and ensures we can use ?. in more places where it previously may have been useful, but could not be used legally due to syntax reasons.
buybackoff 23 minutes ago [-]
> is this actually needed
Yes, actually. I did write it multiple times naturally only to realize it was not supported yet. The pattern is very intuitive.
estimator7292 16 minutes ago [-]
I don't really get this obsessive insistence of purging the language of null checks. (And "if(x is not null)" is not an improvement of any kind)
It feels like Microsoft just wants C# to be Python or whatever and the language is losing its value and identity. It's becoming bland, messy, and complicated for all the same reasons they keep increasing padding between UI elements. It's very "me too" and I'm becoming less and less interested in what I used to consider my native language.
I used to write any and all little throwaway programs and utilities in C#, but it just keeps getting more and more fussy. Like Python, or maybe java. Nowadays I reach for C++. It's more complicated, but at least it's stable and not trying to rip itself apart.
vjvjvjvjghv 5 hours ago [-]
It's starting to feel like C# is going down the path of C++. Tons of features that introduce subtleties and everybody has their own set of pet features they know how to use.
But the code gets really hard to understand when you encounter code that uses a subset you aren't familiar with. I remember staring at C++ codebases for days trying to figure out what is going on there. There was nothing wrong with the code. I just wasn't too familiar with the particular features they were using.
koyote 4 hours ago [-]
There's a couple reasons I disagree with you on this (at the moment; as given enough time I am sure C# will also jump the shark):
* The above is just applying an existing (useful) feature to a different context. So there isn't really much learning needed, it now just 'works as expected' for assignments and I'd expect most C# engineers to start using this from the get go.
* As a C# and C++ developer, I am always excited to hear about new things coming in C++ that purportedly fix some old pain points. But in the last decade I'd say the vast majority of those have been implemented in awful ways that actually make the problem worse (e.g. modules, filesystem, ...). C#'s new features always seem pretty sound to me on the other hand.
swoorup 14 minutes ago [-]
A C# dev can't complain, because complexity creates job.
jayd16 4 hours ago [-]
The difference is the language syntax choices are good. There's no "what does this const refer to" type confusion.
vjvjvjvjghv 2 hours ago [-]
Agreed about the syntax choices. Much better than C++. The language is just getting a little too big for my taste.
peterashford 5 hours ago [-]
I'm a Java fan so I'm contractually required to dis c#, but actually I kinda like this. It reduces boilerplate. Yes, it could be abused but this is what code review is for.
moomin 2 hours ago [-]
You’re not wrong. Every language feature that gets added there’s someone who wants to stop the clock and hold the language definition in place because “people might misuse it” or “people might not be familiar with it”. It’s not language specific, it’s everywhere.
ffsm8 1 hours ago [-]
Still, enabling ?. Access on the left side of the equals (assigning) feels like a serious anti pattern to me
I struggle to even see how anyone would prefer that over an explicit if before assigning.
Having that on the right side (attribute reference) is great, but that was already available as far as I understood the post...
rkomorn 26 minutes ago [-]
As someone who comes from a language with no ? (or equivalent) who only dabbles in C#, it actually seemed a little weird to me that this was one of the contexts where it wasn't usable.
So as a casual observer, I'd say it brings more consistency.
But also as a casual observer, my opinion is low-value.
fakwandi_priv 23 minutes ago [-]
The point the article is trying to make is that it reduces boilerplate, wouldn't be surprised if this gets added to TS in the next year of two.
bandyaboot 5 hours ago [-]
I’m having a hard time imagining where this is useful. If I’m trying to assign to a property, but encounter an intermediate null value in the access chain, just skipping the assignment is almost never going to be what I want to do. I’m going to want to initialize that null value.
moogly 5 hours ago [-]
I'm also not sure I have a lot of code where this would be useful, but adding it to the language I don't feel makes it worse in any way; in fact, it makes it more consistent since you can do conditional null reads and conditional null method invocations (w/ `?.Invoke()`), so why not writes too.
layer8 5 hours ago [-]
“Why not?” is never a good-enough reason to add a new language feature.
If it’s rarely used, people may misinterpret whether the RHS is evaluated or not when the LHS doesn’t exist (I don’t actually know which it is).
Optional operations and missing properties often require subtle consideration of how to handle them. You don’t want to make it too easy to say “whatever”.
moogly 5 hours ago [-]
> people may misinterpret whether the RHS is evaluated or not when the LHS doesn’t exist
I fully expect no RHS evaluation in that case. I think the fear is misplaced; it's one of those "why can't I do that when I can do this" IMO. If you're concerned, enable the analyzer to forbid it.
There are already some really overly paranoid analyzers in the full normal set that makes me wonder how masochistic one can be...
2 hours ago [-]
Quarrelsome 5 hours ago [-]
improving crappy codebases without breaking anything.
Bad .NET developers are forever doing null checks because they write weird and unreliable code. So if you have to fix up some pile of rotting code, it can help you slowly iterate towards something more sane over time.
For example in my last gig, the original devs didn't understand typing, so they were forever writing typing code at low levels to check types (with marker interfaces) to basically implement classes outside of the classes. Then of course there was lots of setting of mutable state outside of constructors, so basically null was always in play at any moment at any time.
I would have loved this feature while working for them, but alas; they were still on 4.8.1 and refused to allow me to upgrade the codebase to .net core, so it wouldn't have helped anyway.
rahkiin 10 minutes ago [-]
These null checks are actually for Optionals in the type system.
The whole standard library and many better packages use nullability and thus indicate what can and cannot be null ever.
And structs can never be null.
So no, c# are not constantly null-checking more than in Rust
zdragnar 3 hours ago [-]
Unfortunately, I suspect this will just makes it easier to keep writing sloppy code.
1 hours ago [-]
5 hours ago [-]
mkoubaa 5 hours ago [-]
Monad-maxxing has ruined many a language
chowells 3 hours ago [-]
This is a functor, not a monad. Also, it's implemented really poorly. If only more languages actually implemented monads well. You wouldn't need special case junk like this.
rkagerer 6 hours ago [-]
More concise? Yes.
More readable? I'm less convinced on that one.
Some of those edge cases and their effects can get pretty nuanced. I fear this will get overused exactly as the article warns, and I'm going to see bloody questions marks all over codebases. I hope in time the mental overhead to interpret exactly what they're doing will become muscle memory...
larusso 2 hours ago [-]
When the first wave of null check operators came out our code bases filled up with ? operators. I luckily had used the operator in swift and rust to somewhat know what it can do and what not. Worse the fact that unlike rust the ? operator only works on null. So people started to use null as an optional value. And I think that is at the core the problem of the feature. C# is not advertising or using this themselves in this way. I think the nullable checks etc are great way to keep NPE under control. But they can promote lazy programming as well. In code reviews more often than not the question comes up, when somebody is using ? either as operator or as nullable type like ‘string?’, are you sure the value should be nullable? And why are you hiding a bug with a conditional access when the value should never be null in the first place.
DimmieMan 5 hours ago [-]
And more better? I'm not sure either.
In all these examples I feel something must be very wrong with the data model if you're conditionally assigning 3 levels down.
At least the previous syntax the annoyingness to write it might prompt you to fix it, and it's clear when you're reading it that something ain't right. Now there's a cute syntax to cover it up and pretend everything is okay.
If you start seeing question marks all over the codebase most of us are going to stop transpiling them in our head and start subconsciously filtering them out and miss a lot of stupid mistakes too.
estimator7292 5 minutes ago [-]
This is something I see in newbie or extremely lazy code. You have some nested object without a sane constructor and you have to conditionally construct a list three levels down.
This is a fantastic way to make such nasty behavior easier.
And agreed on the question mark fatigue. This happened to a project in my last job. Because nullable types were disabled, everything had question marks because you can't just wish away null values. So we all became blind and several nullref exceptions persisted for far too long.
I'm not convinced this is any better.
monocularvision 5 hours ago [-]
Swift has had this from the beginning, and it doesn’t seem to have been a problem.
arwhatever 6 hours ago [-]
What?.could?.possibly?.go?.wrong?.
esafak 6 hours ago [-]
if (This) {
if (is) {
if (much) {
if (better) {
println("I get paid by the brace")
}
}
}
}
kazinator 6 hours ago [-]
False dichotomy. The problem is that the syntax implements a solution that is likely wrong in many situations and pairs with a bad program design. Maybe when we have this:
if (!what)
what = new typeof(what); // default-construct representative instance
if (!what.could)
what.could = new typeof(what.could);
if (!what.could.possibly.go)
what.could.possibly.go = new typeof(what.could.posssibly.go)
// now assignment can take place and actually retain the stored value
// since we may have allocated what, we have to be sure
// we propagate it out of here.
what.could.possibly.go.wrong = important_value();
and not code which throws away the value (and possibly its calculation).
Why would you ever write an assignment, but not expect that it "sticks"? Assignments are pretty important.
What if someone doesn't notice the question marks and proceeds to read the rest of the code thinking that the assignment always takes effect? Is that still readable?
Dylan16807 3 hours ago [-]
> Maybe we want code like this
It should be clear enough that this operator isn't going to run 'new' on your behalf. For layers you want to leave missing, use "?.". For layers you want to construct, use "??=".
> Why would you ever write an assignment, but not expect that it "sticks"? Assignments are pretty important.
If you start with the assignment, then it's important and you want it to go somewhere.
If you start with the variable, then if that variable doesn't have a home you don't need to assign it anything.
So whether you want to skip it depends on the situation.
> What if someone doesn't notice the question marks and proceeds to read the rest of the code thinking that the assignment always takes effect? Is that still readable?
Do you have the same objection with the existing null-conditional operators? Looking at the operators is important and I don't think this makes the "I didn't notice that operator" problem worse in a significant way.
garbagepatch 2 hours ago [-]
I wonder, does the important_value function get called and the value discarded or never called at all? Looks like a footgun if it has side-effects.
Quarrelsome 5 hours ago [-]
NullReferenceException, in line 7.
you didn't null check possibly.go.
esafak 5 hours ago [-]
Just because you can't do assignments like that, it doesn't mean you shouldn't use null coalescing for reads. What exactly could go wrong?
arwhatever 58 minutes ago [-]
Paranoid null checking of every property dereference everywhere (much?.like?.in ?.my?.joke) whether each is ever possibly null or not, usually combined with not thinking through what the code behavior should be for each null case.
(Gets a lot better if you enable nullable references and upgrade the nullable reference warnings to errors.)
Maybe the design is wrong if the code is asked to store values into an incomplete skeleton, and it's just okay to discard them in that case.
huflungdung 3 hours ago [-]
[dead]
h4x0rr 6 hours ago [-]
Oh come on just learn it properly it's not a big deal to read it
vivegi 30 minutes ago [-]
This sounds like a shortcut, unless it isn't.
I have a feeling this is going to make debugging code written just a few months ago incrementally difficult. At least the explicit if statements are easier to follow the intent from months ago.
The syntax is clean though. I'll give it that.
reactordev 7 hours ago [-]
Love to see conciseness for the sake of readability. Honestly I thought this was already a thing until I tried it a year ago…
I’m glad it’s now a thing. It’s an easy win, helps readability and helps to reduce the verbosity of some functions. Love it. Now, make the runtime faster…
coneonthefloor 7 hours ago [-]
I’d rather be explicit. If the value is null then it should be explicitly handled.
I feel like this is another step in the race to add every conceivable feature to a language, for the sake of it.
xboxnolifes 6 hours ago [-]
This is explicit though. The question mark operator is the developer explicitly asking for this behavior.
7 hours ago [-]
4 hours ago [-]
tekdude 4 hours ago [-]
I wonder if this supports a cleaner way to throw when the target property's parent object is null? With null-coalescing assignment, you can do the following which will throw when 'x' is null:
string x = null;
string y = x ?? throw new ArgumentException("x is null");
It would be interesting to try something like:
customer?.Name = newName ?? throw new InvalidOperationException("customer is null");
But I don't know how the language would be able to determine which potential null it was throwing for: 'customer' could be null, but so could 'newName'. I guess... maybe you could do:
(customer ?? throw new InvalidOperationException("customer is null")).Name = newName ?? throw new ArgumentException("newName is null");
But the language already supports that, and it's extremely ugly...
tialaramex 6 hours ago [-]
At least so far, my instinct is that we should turn this off/ ensure it is never turned on, as it seems likely to be a foot gun.
I couldn't imagine what a "Null-Conditional Assignment" would do, and now I see but I don't want this.
Less seriously, I think there's plenty of April Fools opportunity in this space. "Null-Conditional Function Parameters" for example. Suppose we call foo(bar?, baz?) we can now decide that because bar was null, this is actually executing foo(baz) even though that's a completely unrelated overload. Hilarity ensues!
Or what about "Null-Conditional Syntax". If I write ???? inside a namespace block, C# just assumes that when we need stuff which doesn't exist from this namespace it's probably just null anyway, don't stress. Instead of actual work I can just open up a file, paste in ???? and by the time anybody realises none of my new "classes" actually exist or work I've collected my salary anyway.
DeathArrow 46 minutes ago [-]
While this is nice, there are some long requested features like Discriminated Unions that got delayed a lot.
6 hours ago [-]
aldousd666 5 hours ago [-]
Like Ruby safe navigation operator `&` and kotlin, groovy and swift's `?`
LelouBil 6 hours ago [-]
I'm working on a Unity game and I'm so annoyed I can't use all of the new fancy c# features
sieep 7 hours ago [-]
Looks interesting & I'm excited to try this out myself. I like the more verbose null/error handling personally in professional code, but maybe that's because im still working in framework! I'll certainly be using these in my personal projects that'll be on .NET 10
actionfromafar 7 hours ago [-]
You can use newer LangVersion in framework too.
RomanPushkin 3 hours ago [-]
Nice feature! (we had in Ruby for many years)
billmcneale 6 hours ago [-]
> if (customer?.Profile is not null)
>{
> // Null-coalescing (??)
> customer.Profile.Avatar = request.Avatar ?? "./default-avatar.jpg";
>}
Isn't this over engineered? Why not allow the assignment but do nothing if any of the intermediate objects is null (that's how Kotlin does it).
LelouBil 6 hours ago [-]
That's what's new in c#14, it allows you to do
customer?.Profile?.Avatar = "thing"
And will do nothing if the left hand side is null (not throw a null reference exception anymore)
drzaiusx11 6 hours ago [-]
So like ruby's '&.' null safe chaining
Dylan16807 3 hours ago [-]
Yes but they already had it for non-assigning uses.
zulu-inuoe 7 hours ago [-]
I'm looking forward to being able to use this. It doesn't sound like much but those extra three lines and variable assignment is duplicated a ton of times across our codebase so it'll be a nice change
5 hours ago [-]
dionian 3 hours ago [-]
never using nulls is liberating. this is syntactic sugar for dealing with nulls. definitely welcome though, and definitely will be abused (which cant be done when nulls are actually banished)
wslh 7 hours ago [-]
Isn't this more confusing? Because it skip the code if the value is null and I don't think it is normal to follow the flow assuming nothing has happened.
electroly 7 hours ago [-]
That's already the case for the null coalescing operator when it ends in a method call: the method call is skipped if the base is null. For instance, we can invoke event handlers with "myEvent?.Invoke(...);" and the call will be skipped if there are no event handlers registered, and this is the canonical way to do it.
gus_massa 7 hours ago [-]
From the article:
> If config?.Settings is null, the assignment is skipped.
If the right hand expression has side effects, are they run? I guess they do, and that would make the code more predictable.
Izikiel43 5 hours ago [-]
From the article as well:
Side-Effect Prevention
When a null-conditional statement assignment is evaluated, the right-hand side of the expression is not executed unless the left-hand side is defined.
gus_massa 4 hours ago [-]
Thanks. I missed it.
I really dislike that, because it hides the control flow too much. Perhaps I'm biased by Racket, where it's easy to define something weird using macros, but you should not do unexpected weird things.
For example you can define vector-set/drop that writes a value to a position of a vector, but ignores the operation when the position is outside the vector. For example
(vector-set/drop v -2 (print "banana"))
With a macro is possible to skip (print "banana") because -2 is clearly out of range, but if you do that everyone will hate you.
4 hours ago [-]
cjbgkagh 7 hours ago [-]
It’s for the use case where they’d skip it anyway so that would be intended behavior.
accrual 7 hours ago [-]
> I don't think it is normal to follow the flow assuming nothing has happened.
I think it is for situations where the programmer wants to check a child property but the parent object may be null. If the parent is expected to be null sometimes, the syntax lets the programmer express "try to get this value, but if we can't then move on" without the boilerplate of explicitly checking null (which may be a better pattern in some cases).
It's sort of like saying:
- Get the value if you can, else move on. We know it might not be there and it's not a big deal.
v.s.
- The value may not be there, explicitly check and handle it because it should not be null.
BoiledCabbage 6 hours ago [-]
Your summary is almost correct but replace where you used "get" with "set".
kazinator 6 hours ago [-]
Null conditional assignment is bunk.
When you have an expression P which names a mutable place, and you execute P := X, the contract says that P now exhibits the value X, until it is assigned another value.
Conditional assignment fucks this up. When P doesn't exist, X is not stored. (Worse, it may even be that the expression X is not evaluated, depending on how deep the fuckery goes.)
Then when you access the same expression P, the conditional assignment becomes conditional access and you get back some default value like a nil.
Store X, get back nil.
That's like a hardware register, not a durable memory model.
It's okay for a config.connection?.retryPolicy to come up nil when there is no config.connection. It can be that the design makes nil a valid retry policy, representing some default. Or it could be that it is not the case, but the code which uses connection? handles the nil soon afterward.
But a store to config.connection?.retryPolicy not having an effect; that is dodgy.
What you need for config.connection? to do when the expression is being used to calculate a mutable place to assign to is to check that config.connection is null, and in that case, instantiate a representative instance of something which is then assigned to config.connnection, such that the config.connection.retryPolicy place then exists and the assignment can proceed.
This recognizable as a variation on COW (copy-on-write); having some default instance for reading, but allocating something on writing.
In a virtual memory system, freshly allocated memory can appear to contain zero bytes on access due to all of its pages being mapped to a single all-zero frame that exists in the entire system. Conceptually, the hardware could do away with even that all-zero frame and just have a page table entry which says "this is a zero-filled page", so the processor then fakes out the zero values without accessing anything. When the nonexistent page is written, then it gets the backing storage.
In order to instantiate settings.connection? we need to know what that has to be. If we have a static type system, it can come from that: the connection member is of some declared type of which a representative instance can be produced with all constructor parameters defaulted. Under a dynamic paradigm, the settings object can have a handler for this: a request to materialize a field of the object that is required for an assignment.
If you don't want a representative config.connection to be created when config.connection?.retryPolicy is assigned, preferring instead that config.connection stays null, and the assignment is sent to the bit buckets, you have incredibly bad taste and a poor understanding of software engineering and programming language design --- and the design of your program is scatter-brained accordingly.
Dylan16807 3 hours ago [-]
A representative config.connection being made out of nothing sounds pretty bad to me. If you want to make sure the value doesn't disappear, you shouldn't be using conditional assignment in the first place.
The config example isn't the best, but instead imagine if it was just connection.?retryPolicy. After you set connection?.retryPolicy it would be weird for reading it back to be null. But it would be just as weird for connection?.retryPolicy to not be null when we never established a connection in the first place.
The copy on write analogy is tempting but what you're describing only works when the default value is entirely made of nulls. If you need anything that isn't null, you need to actually make an object (either upfront or on first access). And if you do that, you don't need ?. anymore.
kazinator 3 hours ago [-]
Someone is going to run into a null exception in an assignment and just throw in the question mark to shut it up, not thinking about the value disappearing.
That's the mindset the feature is developed for (and by).
NetMageSCW 5 hours ago [-]
Apparently you do t use if in your code?
LAC-Tech 5 hours ago [-]
Is .NET entering its twilight years as a tech people build new things with?
I just can't imagine Gen Z wanting to start a project in C#.
I realise there are still .NET shops, and I still talk to people who do it daily, but ours is a field driven by fashion whether we care to admit or not - and C# just does not feel as fashionable as it once did
(I'm a former C# dev, up until 2020)
koyote 4 hours ago [-]
I personally can't think of an all-rounder language that is better than C#.
It's fast, has great tooling, powerful, extremely productive for working with large code bases and runs 'anywhere'.
JS has lost against TS which is basically C# for web (both designed by the same person) and Python is not really something you should build large applications with (execution speed + maintenance issues).
What do you believe is the current language du jour?
rubenvanwyk 55 minutes ago [-]
Golang is often the default now. As someone who’s new to backend development, I’ve been exploring C# and can’t understand why it’s not the default. I think C# primarily has a marketing problem.
Merad 4 hours ago [-]
I've been using .Net for almost 20 years, professionally for half that time, and I feel like excitement and momentum in the community has only been increasing.
BrouteMinou 1 hours ago [-]
Same story here. I decided lately to focus more on .net and, let's say, abandon Java.
It's portable, fast, productive and well supported by a massive corp. It's not just a "language du jour", it's here to stay.
There are plenty of job in dotnet where I live: old, new, startups...
I am the momentum!
EVa5I7bHFq9mnYK 48 minutes ago [-]
Well, JavaScript is older than C# and still poppin. Gen Z eats it up. C# too, for unity gamez.
recursivecaveat 4 hours ago [-]
It is definitely out of fashion, most directly in comparison to Go I suppose. It seems like they tried with .NET Core, but were not able to provide an appealing and coherent enough on-ramp. The ongoing death of native windows applications not helping either certainly.
It feels like the C# designers have a hard time saying "no" to ideas coming their way. It's one of my biggest annoyances with this otherwise nice language. At this point, C# has over 120 keywords (incl. contextual ones) [0]. This is almost twice as much as Java (68) [1], and 5 times as much as Go (25) [2]. And for what? We're trading brevity for complexity.
[0]: https://learn.microsoft.com/en-us/dotnet/csharp/language-ref... keywords/
[1]: https://en.wikipedia.org/wiki/List_of_Java_keywords
[2]: https://go.dev/ref/spec#Keywords
So this is not exactly a new feature, it just removes a gotcha from an old one and ensures we can use ?. in more places where it previously may have been useful, but could not be used legally due to syntax reasons.
Yes, actually. I did write it multiple times naturally only to realize it was not supported yet. The pattern is very intuitive.
It feels like Microsoft just wants C# to be Python or whatever and the language is losing its value and identity. It's becoming bland, messy, and complicated for all the same reasons they keep increasing padding between UI elements. It's very "me too" and I'm becoming less and less interested in what I used to consider my native language.
I used to write any and all little throwaway programs and utilities in C#, but it just keeps getting more and more fussy. Like Python, or maybe java. Nowadays I reach for C++. It's more complicated, but at least it's stable and not trying to rip itself apart.
But the code gets really hard to understand when you encounter code that uses a subset you aren't familiar with. I remember staring at C++ codebases for days trying to figure out what is going on there. There was nothing wrong with the code. I just wasn't too familiar with the particular features they were using.
* The above is just applying an existing (useful) feature to a different context. So there isn't really much learning needed, it now just 'works as expected' for assignments and I'd expect most C# engineers to start using this from the get go.
* As a C# and C++ developer, I am always excited to hear about new things coming in C++ that purportedly fix some old pain points. But in the last decade I'd say the vast majority of those have been implemented in awful ways that actually make the problem worse (e.g. modules, filesystem, ...). C#'s new features always seem pretty sound to me on the other hand.
I struggle to even see how anyone would prefer that over an explicit if before assigning.
Having that on the right side (attribute reference) is great, but that was already available as far as I understood the post...
So as a casual observer, I'd say it brings more consistency.
But also as a casual observer, my opinion is low-value.
If it’s rarely used, people may misinterpret whether the RHS is evaluated or not when the LHS doesn’t exist (I don’t actually know which it is).
Optional operations and missing properties often require subtle consideration of how to handle them. You don’t want to make it too easy to say “whatever”.
I fully expect no RHS evaluation in that case. I think the fear is misplaced; it's one of those "why can't I do that when I can do this" IMO. If you're concerned, enable the analyzer to forbid it.
There are already some really overly paranoid analyzers in the full normal set that makes me wonder how masochistic one can be...
For example in my last gig, the original devs didn't understand typing, so they were forever writing typing code at low levels to check types (with marker interfaces) to basically implement classes outside of the classes. Then of course there was lots of setting of mutable state outside of constructors, so basically null was always in play at any moment at any time.
I would have loved this feature while working for them, but alas; they were still on 4.8.1 and refused to allow me to upgrade the codebase to .net core, so it wouldn't have helped anyway.
So no, c# are not constantly null-checking more than in Rust
More readable? I'm less convinced on that one.
Some of those edge cases and their effects can get pretty nuanced. I fear this will get overused exactly as the article warns, and I'm going to see bloody questions marks all over codebases. I hope in time the mental overhead to interpret exactly what they're doing will become muscle memory...
In all these examples I feel something must be very wrong with the data model if you're conditionally assigning 3 levels down.
At least the previous syntax the annoyingness to write it might prompt you to fix it, and it's clear when you're reading it that something ain't right. Now there's a cute syntax to cover it up and pretend everything is okay.
If you start seeing question marks all over the codebase most of us are going to stop transpiling them in our head and start subconsciously filtering them out and miss a lot of stupid mistakes too.
This is a fantastic way to make such nasty behavior easier.
And agreed on the question mark fatigue. This happened to a project in my last job. Because nullable types were disabled, everything had question marks because you can't just wish away null values. So we all became blind and several nullref exceptions persisted for far too long.
I'm not convinced this is any better.
Why would you ever write an assignment, but not expect that it "sticks"? Assignments are pretty important.
What if someone doesn't notice the question marks and proceeds to read the rest of the code thinking that the assignment always takes effect? Is that still readable?
It should be clear enough that this operator isn't going to run 'new' on your behalf. For layers you want to leave missing, use "?.". For layers you want to construct, use "??=".
> Why would you ever write an assignment, but not expect that it "sticks"? Assignments are pretty important.
If you start with the assignment, then it's important and you want it to go somewhere.
If you start with the variable, then if that variable doesn't have a home you don't need to assign it anything.
So whether you want to skip it depends on the situation.
> What if someone doesn't notice the question marks and proceeds to read the rest of the code thinking that the assignment always takes effect? Is that still readable?
Do you have the same objection with the existing null-conditional operators? Looking at the operators is important and I don't think this makes the "I didn't notice that operator" problem worse in a significant way.
you didn't null check possibly.go.
(Gets a lot better if you enable nullable references and upgrade the nullable reference warnings to errors.)
I have a feeling this is going to make debugging code written just a few months ago incrementally difficult. At least the explicit if statements are easier to follow the intent from months ago.
The syntax is clean though. I'll give it that.
I’m glad it’s now a thing. It’s an easy win, helps readability and helps to reduce the verbosity of some functions. Love it. Now, make the runtime faster…
I feel like this is another step in the race to add every conceivable feature to a language, for the sake of it.
I couldn't imagine what a "Null-Conditional Assignment" would do, and now I see but I don't want this.
Less seriously, I think there's plenty of April Fools opportunity in this space. "Null-Conditional Function Parameters" for example. Suppose we call foo(bar?, baz?) we can now decide that because bar was null, this is actually executing foo(baz) even though that's a completely unrelated overload. Hilarity ensues!
Or what about "Null-Conditional Syntax". If I write ???? inside a namespace block, C# just assumes that when we need stuff which doesn't exist from this namespace it's probably just null anyway, don't stress. Instead of actual work I can just open up a file, paste in ???? and by the time anybody realises none of my new "classes" actually exist or work I've collected my salary anyway.
Isn't this over engineered? Why not allow the assignment but do nothing if any of the intermediate objects is null (that's how Kotlin does it).
> If config?.Settings is null, the assignment is skipped.
If the right hand expression has side effects, are they run? I guess they do, and that would make the code more predictable.
Side-Effect Prevention When a null-conditional statement assignment is evaluated, the right-hand side of the expression is not executed unless the left-hand side is defined.
I really dislike that, because it hides the control flow too much. Perhaps I'm biased by Racket, where it's easy to define something weird using macros, but you should not do unexpected weird things.
For example you can define vector-set/drop that writes a value to a position of a vector, but ignores the operation when the position is outside the vector. For example
With a macro is possible to skip (print "banana") because -2 is clearly out of range, but if you do that everyone will hate you.I think it is for situations where the programmer wants to check a child property but the parent object may be null. If the parent is expected to be null sometimes, the syntax lets the programmer express "try to get this value, but if we can't then move on" without the boilerplate of explicitly checking null (which may be a better pattern in some cases).
It's sort of like saying:
- Get the value if you can, else move on. We know it might not be there and it's not a big deal.
v.s.
- The value may not be there, explicitly check and handle it because it should not be null.
When you have an expression P which names a mutable place, and you execute P := X, the contract says that P now exhibits the value X, until it is assigned another value.
Conditional assignment fucks this up. When P doesn't exist, X is not stored. (Worse, it may even be that the expression X is not evaluated, depending on how deep the fuckery goes.)
Then when you access the same expression P, the conditional assignment becomes conditional access and you get back some default value like a nil.
Store X, get back nil.
That's like a hardware register, not a durable memory model.
It's okay for a config.connection?.retryPolicy to come up nil when there is no config.connection. It can be that the design makes nil a valid retry policy, representing some default. Or it could be that it is not the case, but the code which uses connection? handles the nil soon afterward.
But a store to config.connection?.retryPolicy not having an effect; that is dodgy.
What you need for config.connection? to do when the expression is being used to calculate a mutable place to assign to is to check that config.connection is null, and in that case, instantiate a representative instance of something which is then assigned to config.connnection, such that the config.connection.retryPolicy place then exists and the assignment can proceed.
This recognizable as a variation on COW (copy-on-write); having some default instance for reading, but allocating something on writing.
In a virtual memory system, freshly allocated memory can appear to contain zero bytes on access due to all of its pages being mapped to a single all-zero frame that exists in the entire system. Conceptually, the hardware could do away with even that all-zero frame and just have a page table entry which says "this is a zero-filled page", so the processor then fakes out the zero values without accessing anything. When the nonexistent page is written, then it gets the backing storage.
In order to instantiate settings.connection? we need to know what that has to be. If we have a static type system, it can come from that: the connection member is of some declared type of which a representative instance can be produced with all constructor parameters defaulted. Under a dynamic paradigm, the settings object can have a handler for this: a request to materialize a field of the object that is required for an assignment.
If you don't want a representative config.connection to be created when config.connection?.retryPolicy is assigned, preferring instead that config.connection stays null, and the assignment is sent to the bit buckets, you have incredibly bad taste and a poor understanding of software engineering and programming language design --- and the design of your program is scatter-brained accordingly.
The config example isn't the best, but instead imagine if it was just connection.?retryPolicy. After you set connection?.retryPolicy it would be weird for reading it back to be null. But it would be just as weird for connection?.retryPolicy to not be null when we never established a connection in the first place.
The copy on write analogy is tempting but what you're describing only works when the default value is entirely made of nulls. If you need anything that isn't null, you need to actually make an object (either upfront or on first access). And if you do that, you don't need ?. anymore.
That's the mindset the feature is developed for (and by).
I just can't imagine Gen Z wanting to start a project in C#.
I realise there are still .NET shops, and I still talk to people who do it daily, but ours is a field driven by fashion whether we care to admit or not - and C# just does not feel as fashionable as it once did
(I'm a former C# dev, up until 2020)
JS has lost against TS which is basically C# for web (both designed by the same person) and Python is not really something you should build large applications with (execution speed + maintenance issues).
What do you believe is the current language du jour?
It's portable, fast, productive and well supported by a massive corp. It's not just a "language du jour", it's here to stay.
There are plenty of job in dotnet where I live: old, new, startups...
I am the momentum!