dotnet / csharplang

The official repo for the design of the C# programming language
11.48k stars 1.02k forks source link

Discussion: `null` evaluation operator to boolean #196

Closed lachbaer closed 7 years ago

lachbaer commented 7 years ago

Since the beginning of C# I dislike the extensive use of obj == null comparisons.

On one hand C# is quite verbose and needs a handful of charaters to express a simple thing, on the other hand there is done quite effort to shorten code, like e.g. expression bodies, null-coalescing operators, etc.

From C I much like the fact that null evaluates to boolean false and !null to true.

I'm not sure about a possible syntax. Because C# is type safe and the existing obj == null has its reason, an operator must be something conspicuous to make clear that the following reference is evaluated to true or false. It could be e.g. an unary ? operator (like e.g. unary +, - and ~)

if (obj != null) DoIfNotNull();
// goes to 
if ( ?obj ) DoIfNotNull();

if (obj == null) obj = new Object();
// goes to
if ( ! ?obj) obj = new Object();

Together with #157, where putting the not operator ! in front of for and while clauses to negate the expression it could be something like

if ?( obj ) DoIfNotNull();
if !?( obj) obj = new Object();
HaloFour commented 7 years ago

I believe that the decision that C# could only evaluate Boolean conditions (and limits what can be considered such) was very intentional, specifically to avoid the assortment of logic errors caused by minor typos in the C language. For example, your change would make the following legal, and very wrong:

if (obj = null) { // oops!
    DoIfNotNull();
}
svick commented 7 years ago

@HaloFour I agree that the code you showed should not be allowed, but this proposal does not suggest to allow it. Instead, you would have to include the ? operator:

if (?(obj = null))

Or:

if ?(obj = null)

I think this means the error is not easy to make this way, which is why I personally don't oppose this proposal.

(I'm curious why so many people downvoted this. Did they misunderstood the proposal too? Or do they just not like the syntax or the proposal in general?)

jnm2 commented 7 years ago

Didn't downvote but I'm far from convinced that adding syntax makes null checking any more readable than == null already is.

lachbaer commented 7 years ago

@HaloFour It is good that it was made intentionally, actually I think that it is a side product of type safety.

I do not intend to allow obj = null, at least not if explicitly stated to be allowed. It should be something like

String s = null;
if ( s?.Length > 0) { ... }

where first s?.Length is converted to nullable type int? and then compared to (int?) 0, what produces false in case of s being null. But here it is just for obj == null, like in C and type safe.

@jnm2 I want to avoid writing things like

while ( ( item  = collection?.Next() ) != null ) { ... }

when they can be written

while ( ?! item = collection?.Next() ) { ... }

I think it would be enough to make this available to all if-condition like contexts, where the condition evaluates to true like in C where it is not null, 0, 0.0 or false, respectively default(T).

So it actually comes down to an operator that implicitly cast every type to (bool). And that operator should be brief.

lachbaer commented 7 years ago

Such an operator could be ?!, because ? is already used in many nullable scenarios and ! just stating not, so ?! => null not [not null]. It should have the same precedence as assignment operators, evaluated from right to left. And together with #157 it could be written before the condition braces

while ?!( item = collection?.Next() ) { ... }
MillKaDe commented 7 years ago

As far as I understand the proposal of @lachbaer, he did not suggest that C# adopts the automatic conversion of everything to bool.

Instead he suggests a unary prefix operator '?' for all reference type, that checks if the reference is not null, e.g. something like this:

static public bool operator<T> ? (this T reference) where T : class
{
  return reference != null;
}

Yes, thats not valid C#, but I hope everybody understands what it means anyway ...

Instead of the automatic conversion to bool from C/C++, C# would require explicit usage of the suggested new 'notnull' operator to 'convert' the reference to bool.

For example ..

if (?obj) DoSomethingWith (obj);

.. would be a shorter version of ..

if (obj != null) DoSomethingWith (obj);

That shorter version is at least as readable as the null-conditional operator '?.' which was introduced in C# 6:

obj?.DoSomething ();

Because bool already has an unary prefix operator ! to negate a bool, checking for null (instead of not null) would be possible by combiniation of '!' and '?', e.g. writing ..

if (!(?obj)) obj = new FooBar ();

.. or even better (if precedence and associativity of the new operator are defined apropriately) without additional parenthesis ..

if (!?obj) obj = new FooBar ();

.. as a shorter version of ..

if (obj == null) obj = new FooBar ();

The motivation is of course to have a syntax for reference checking, that is almost as short as in C/C++, but without inheriting all the problems and bugs of automatic conversion to bool.

In my humble opinion, the suggested notnull operator would be really nice to have in a future version of C#.

Update: I would prefer if (?obj) .. over if ?(obj) ...

MillKaDe commented 7 years ago

Hmm, looks like there are several questions: 1) do we want/need an operator (e.g. '?') to check for (x != null) ? 2) whats the best syntax ? 3) do we want/need another operator (e.g. '!?') to check for (x == null) ? 4) can we use just use the first operator (?) combined with bool negation (!) instead of a second operator ? 5) shall the operator be defined only for references (x != null) or extended to value types (x != 0) or (x != default(X)) ?

stepanbenes commented 7 years ago

If you like to write so many question marks and exclamation marks I just want to let you know that there is this beautiful language Ook?!.

lachbaer commented 7 years ago

Meanwhile I would go with =! default(T)

static public bool operator<T> ? (this T value)
    => value != default(T);

because there are ongoing discussions (sorry, don't have the threads by hand) about defining own default's for value types, defaults for Tuples and so on.

if (obj == null) obj = new FooBar ();
// vs.
if (!?obj) obj = new FooBar ();

I think !?obj is a bit cryptic and confusing in this way and shouldn't be used (maybe issue a warning), but it would be legal. It would be better to write that example with

obj = obj ?? new FooBar();
// or with roslyn/dotnet#205
obj ??= new FooBar();
jnm2 commented 7 years ago

@lachbaer

@jnm2 I want to avoid writing things like

while ( ( item  = collection?.Next() ) != null ) { ... }

when they can be written

while ( ?! item = collection?.Next() ) { ... }

I find the first example much more readable and natural, so there is definitely subjectivity to this.

Also, !item = x is currently the same as saying (!item) = x which is illegal (CS0131), so I would expect that you'd need the parentheses:

while ( ?!(item = collection?.Next()) ) { ... }

If you remove the parentheses, I'd expect operator precedence to be the equivalent of (?!item) = collection?.Next() which should fail with CS0131.

Since the parentheses are necessary, the question is whether ?(expression) is more readable than (expression) == null. I'm still far from convinced.

lachbaer commented 7 years ago

I don't feel as ? is the right character for this, because by now the ? operator says "if it is set, then use it", like with ?. and ??.

But here the negation is more of a subject: if (obj != null) ThenUseIt();

Would !! stand out better?

if ( !! obj ) { UseObject(); }

! is a unary boolean already and ! ! would override itself, so to say being neutral. I doubt anybody wrote this ( or bool b = !!!!!!!!!! true; ๐Ÿ˜‰ ) in actual code, so two !! would take precedence during parsing.

HaloFour commented 7 years ago

@svick

You're right, I jumped the gun on responding there.

That said, this proposal seems to be about adding a "Truthiness" operator to C#. What real benefit does that provide? Is actually explicitly specifying the comparison being performed such a burden? I don't think so. Even assuming C# doesn't totally botch up "Truthiness" to the extent that JavaScript did I don't see any need to have some manner through which arbitrary values are evaluated to a Boolean result. It hides too many details, such as the specific type and what that comparison really means. You save typing a few characters at the expense of understanding what that code will do later. Not worth it, in my opinion.

What would the following mean? Null check? Zero check? Both?

int? x = 0;
if (?x) { ... }
lachbaer commented 7 years ago

@HaloFour As stated above I don't think that ? is the right operator for this. I'll go with !! for now.

What would the following mean? Null check? Zero check? Both?

int? x = 0;
if (!!x) { ... }

it would mean "if x is set to something other than it's default value". For ref types that would be x!=null, for nullable type it would also be null, so the above statement would be true. But you are right that one could understand it as if (x!=0). So maybe in the case of standard value types it should just be both, giving the meaning "if x has something valuable to work with".

int? x = 0;
if ( x.HasValue && x != 0 ) { ... }  // or just `x != 0`
HaloFour commented 7 years ago

@lachbaer

As stated above I don't think that ? is the right operator for this. I'll go with !! for now.

Fairly sure that will result in ambiguities. ! is already the unary negation operator and the compiler allows you to apply it multiple times if you see fit. bool b = false; if(!!b) { } is already perfectly legal.

Either way, the syntax of the operator is less important than what it does. It hides how the comparisons are made, and in the case of nullable structs there aren't "right answers".

lachbaer commented 7 years ago

@HaloFour

and in the case of nullable structs there aren't "right answers"

For me =! default(T) is quite definite, and if it is extended for nullable primitive types to =! 0 (or alike) I think that's also clear. No, I don't see any ambiguities.

PS: The character is currently not of interest, let it be ยง if you like ๐Ÿ˜‰ I just think that ?is definitely not appropriate

HaloFour commented 7 years ago

@lachbaer

So you expect the following to print yay? I certainly wouldn't. I highly doubt that the majority of developers would. It's very unintuitive.

bool? b = false;
if (ยงb) {
    Console.WriteLine("yay?");
}
lachbaer commented 7 years ago

@HaloFour No it wouldnt printyay`. I will quote myself:

it is extended for nullable primitive types to =! 0 (or alike)

So in this case it would be equal to

bool? b = false;
if (b.GetValueOrDefault() != default(bool)) { Console.WriteLine("yay?"); } 
MillKaDe commented 7 years ago

After thinking about the suggestion for a couple of hours, let me bore you with my latest brain dump ..

1) The main question is:

Do we need or want an alternative way to check if some variable or expression x of type X has or has not default value of X (default(X) = null or false or 0 or 0.0 or whatever).

2) The motivation is to have some shorter syntax, which does not contain the default value

3) No matter what the syntax will be, the result of the check is always either true or false, so the result type is always a simple two-valued bool (and not bool? or anything else with more than two states/values).

4) Some possible types X of the variable or expression x to be checked for default value are:

5) Comparing ..

6) It is already possible to define two extension functions for checking references:

   public static class Ext0 {
     public static bool Not0<C> (this C c) where C : class => c != null;
     public static bool Is0<C> (this C c) where C : class => c == null;
   }

   void Usage (C c) {
     // some checks for not-null
     if (c != null) FooBar (); // classic
     if (c.Not0 ()) FooBar (); // not that much better
     // some checks for null
     if (c == null) FooBar (); // classic
     if (c.Is0 ()) FooBar (); // not that much better either
   }

If C# had extension properties, we could omit the (), but it would still look ugly.

7) The check of x could be used:

8) Conclusion:

9) Some syntax examples for comparison:

C# C# with ? C/C++
if (r != null) if (?r) if (r)
if (r == null) if (!?r) if (!r)
while (r != null) while (?r) while (r)
for (var n = list.head; n != null; n = n.next) for (var n = list.head; ?n; n = n.next) for (var n = list.head; n; n = n.next)
x != null ? a : b ?x ? a : b x ? a : b
x == null ? a : b !?x ? a : b !x ? a : b
lachbaer commented 7 years ago

I tried to write some static methods to simulate an operator

        public static bool asbool1<T>(T value) where T : class
            => value != null;
        public static bool asbool2<T>(T value) where T : struct
            => ! value.Equals( default(T) );
        public static bool asbool3<T>(T? value) where T : struct
            => ! value.GetValueOrDefault().Equals(default(T));

It is not possible to give all 3 methods the same name. That, too, encourages me to vote for an appropriate operator. I really still like the idea of having the explicit possibility of C-like boolean evaluations,

MgSam commented 7 years ago

I think null coalescing assignments satisfies a big part of the use case motivating this request.

MillKaDe commented 7 years ago

@MgSam : No, 'null coalescing assignments' covers only the special case, where the checked reference is assigned after the check:

r ??= new Foo ();
if (r == null) r = new Foo ();

This suggestion is about any reference check, independent of the code after the check. The idea is to adopt an existing and frequently used idiom from C/C++, but without inheriting the well known problems.

lachbaer commented 7 years ago

Two questions still arise to me:

1. How to deal with Nullable Types?

The operator should work on the default(T). For nullable-value-types that would be null also. But wouldn't it be better to have it working on Nullable<T>.GetValueOrDefault()?

2. Is ?the right operator

? is actually used for null-implying conditions. But here we are talking also about figures like 0, 0.0, etc. Especially for the nullably-value-types, ? would more stand for null-only and not for GetValueOrDefault().

lachbaer commented 7 years ago

May be a single @ sign would stand out more. It can be read as a "h@s" sign, claiming that the following expression has a powerful value other than 0 or null or false, thus emulating the C/C++ meaning of "truth".

@ targets the def@ult values and in contrast to ? it wouldn't emphasize the null meaning. Also some expressions look more meaningful (e.g. "!@") and emphasize the special character of the condition, see below.

C# C# with @ C/C++
if (r != null) if (@ r ) if (r)
if (r == null) if (!@ r) if (!r)
while (r != null) while (@ r ) while (r)
for (var n = list.head; n != null; n = n.next) for (var n = list.head; @n; n = n.next) for (var n = list.head; n; n = n.next)
x != null ? a : b @x ? a : b x ? a : b
x == null ? a : b ! @x ? a : b !x ? a : b
DavidArno commented 7 years ago

The key to your static methods is to treat the struct version as a special case, as it can't be null and so give it a different name. Then all works as you want:

public static bool IsNullOrDefault<T>(T value) where T : class => 
    value != null;

public static bool IsNullOrDefault<T>(T? value) where T : struct =>
    !value.GetValueOrDefault().Equals(default(T));

public static bool IsDefault<T>(T value) where T : struct => 
    !value.Equals(default(T));
yaakov-h commented 7 years ago

@lachbaer If @x were to mean x != null or x != default(T), then what about @"hello"? That would be ambiguous between a string, or an always-true expression.

lachbaer commented 7 years ago

@DavidArno :thumbsup: And now as an operator, please. ๐Ÿ˜

@yaakov-h Yes, that came to my mind yesterday night as well.

An unary %-operator would be available, o/o could mean "boolean", or "bunch of 0s". But this would rather mean to negate the meaning of "not null (0)" to "is null (0)". Thus the complete operator would be "!%":

C# C# with % (!%) C/C++
if (r != null) if (!% r ) if (r)
if (r == null) if (% r) if (!r)
while (r != null) while (!% r ) while (r)
for (var n = list.head; n != null; n = n.next) for (var n = list.head; !%n; n = n.next) for (var n = list.head; n; n = n.next)
x != null ? a : b !%x ? a : b x ? a : b
x == null ? a : b %x ? a : b !x ? a : b

This doesn't look so intuitive to me.

yaakov-h commented 7 years ago

Probably the most confusing thing is that your proposal seems to be trying to bring an aspect of C into C#, but completely inverts the boolean result of the expression. The 2nd and 3rd columns of your table are complete opposites.

lachbaer commented 7 years ago

@yaakov-h

The 2nd and 3rd columns of your table are complete opposites.

That's what I meant with "This doesn't look so intuitive to me"

MillKaDe commented 7 years ago

Above, @lachbaer asked:

How to deal with Nullable Types?

1) A declared, but not explicitely initialized Nullable<T> is filled with zero bits. A bunch of zero bits of type Nullable<T> t means

2) So what would / should be the meaning of the check operator when applied to a Nullable ? Conceptually, Nullable<T> is a wrapper around a (non-nullable) value type T, that (by wrapping) adds an additional value null. If it is a wrapper, I think it makes sense to progress from outside to inside. So first we have to check for null to know if it has a value at all, and then we can check the value for 0 or default (T).

3) First, lets just check if t has a value:

   Nullable<T> t; // or shorter: `T? t`
   if (t.HasValue) ...; // [1a] old
   if (t != null) ...; // [2a] old
   if (?t) ..; // [3a] new

Next we check if t has a value, and if that value is different from the default value:

   if (t.HasValue && t.Value != default (T)) ...; // [1b] old
   if (t != null && t.Value != default (T)) ...; // [2b] old
   if (?t && t.Value != default (T)) ...; // [3b] new

4) With the new check operator, t.Value != default (T) could be shortened to ?(t.Value) :

   if (t.HasValue && ?(t.Value)) ...; // [1c]
   if (t != null && ?(t.Value)) ...; // [2c]
   if (?t && ?(t.Value)) ...; // [3c]

And maybe the third line [3c] could be shortened even more to:

   if (?(t?.Value)) ...; // [4c] new

If precendence and associativity are defined wisely, if might be even possible omit the parenthesis:

   if (t.HasValue && ?t.Value) ...; // [1d]
   if (t != null && ?t.Value) ...; // [2d]
   if (?t && ?t.Value) ...; // [3d]
   if (?t?.Value) ...; // [4d]

5) That fourth line [4d] looks a bit weird, so lets write it with explicit parenthesis again to show the evaluation sequence:

   if (?((t?).Value)) ...; // [4e]

This first '?' is the new check operator, but the second '?' is the already existing null-conditional operator applied to nullable t. That null-conditional operator (second '?') is evaluated first:

6) I admit, that ?t?.Value might look confusing. But whenever different operators mix in C# / C++ / C / whatever,

MillKaDe commented 7 years ago

In my previous post I mentioned precedence and associativity of the suggested check operator several times, but did not specify them.

1) Associativity is only relevant for binary operators ...

2) Precedence The suggested check operator should have the same precedence as the other unary operators (e.g. +x, -x, !x, ~x, ...). Since member access x.y and null-conditional member access x?.y are primary operators, they have higher precedence. This allows to omit the inner parenthesis () in

if (?(x?.y)) ... // same meaning as below, but easier to understand
if (?x?.y) ... // same meaning as above

See lines [4c] and [4d] in my previous post.

lachbaer commented 7 years ago

@MillKaDe Thank you very much for your comprehensive exposition! ๐Ÿ˜„ ๐Ÿ‘

First I wasn't sure whether ?t?.Value for Nullable's is the right way, because my intention was to have an operator that 'boolifies' variables directly, like C/C++, without any further sub-expressions (.Value) required.

On the second thought it might be better, more C# alike, not to mix up null and 0. Besides ?x?.Value can already be (syntactically) abbrevated and expressed more clearly:

if ( ?( (t?).Value ) ) ...; 
// eqals:
if (? t.GetValueOrDefault() ) ...;

t.GetValueOrDefault() already returns the default(T) value of T? if t is null, therefore the check will only be true if t has a value different than all bits 0, default(T) respecively.

For Nullables's this view might be the best, because one will most probably use them, when only null - which is the default(Nullable<T>) - has the meaning of "no meaningful value" and 0 already is a valid meaningful value. Otherwise one can use GetValueOrDefault() or @MillKaDe 's expression from above.

So essentially ? comes down to != default(T) - a "verify for non-default value of T"-operator

MillKaDe commented 7 years ago

I think the follwing two lines from two posts above need some clarification:

if (?t && ?t.Value) ...; // [3d]
//  A     B
if (?t?.Value) ...; // [4d]
//  B A

In line [3d] the first part ?t of the condition checks t for null (like t != null or t.HasValue).

In cases where the null check is followed by a member access, we can already use the existing null-conditional member access t?.Value.

That means the first '?' (new null check op) in ?t in line [3d] corresponds to the second '?' (old null-conditional op) in t?.Value in line [4d]. See marks 'A' above.

The second '?' (new null check op) in ?t.Value in line [3d] corresponds to the first '?' (new null check op) in ?t?.Value in line [4d]. See marks 'B' above.

By the way, that is another hint, that '?' might the best pick for the operator character.

dmitriyse commented 7 years ago

Please describe some one who already dived into this problem deeply, what is wrong with this approach: 1) Ref => implicit cast to Boolean now allowed (null === false) (probably with restriction to built-in flow constructions if, while, for, ?:, etc.). 2) || and && operators now can work with reference type operands and (null === false)

?

MillKaDe commented 7 years ago

to @lachbaer

I agree that t.GetValueOrDefault is preferable over ?t?.Value in a context where it is known that t is of type Nullable<T>. But in some non-nullable context, ?x?.y (field y in class x) might still make sense.

lachbaer commented 7 years ago

@MillKaDe Yes ?x?.y of course is valid for explicit field access and really makes sense to me. I even directly understand its represenation, even if it looks a bit awkward on first sight. Just in case of Nullable's GetValueOrDefault could be better, because it directly states that I am interested in the value of it being null or unequal to 0.

@dmitriyse Sorry, I don't understand you. What does === mean? Is it a new feature I don't know about?

dmitriyse commented 7 years ago

Mathematical Identity it's usually defined by symbol โ‰ก. Just read null โ‰ก false or "interpreted as false". I propose not to use any new operator. Just limited implicit cast from null to false. In "if", "while", "for", "?:" and in || and && operators.

if (someRefVar) // allowed
{
}
if (!someRefVar) //not allowed use classic if (someRefVar == null)
{
}

if (someRefA && someRefB) // allowed
{
}

if (someRefA & someRefB) // not allowed
{

}

Advantage - no any new operators, intuitive. Disadvantages - limited use cases.

MillKaDe commented 7 years ago

to @dmitriyse

I fear I don't understand your question(s).

1) Implicit cast to bool There is a loooooong list of possible errors in C/C++ because of implicit cast to bool, e.g.

   // possible typing error, but legal in C
   if (a = b) .. // assigment of b to a, then converted to bool, meaning (b != 0)
   // versus most likely intention
   if (a == b) .. //

We would like to avoid these kind of bugs. This suggestion tries to block those problems by requiring explicit use of this new check operator.

2) Logical operators ('&&', '||') The result of the new '?' operator is bool. So several check can be combined, e.g.

   if (?x && !?y) ..
   while (?x || ?y) ..

3) Operator '===' C# has no triple equal operator (yet). As far as I know, in JavaScript, '===' is a stricter equal then '==', e.g.

   "2" == 2 // is true, cause string "2" has the same 'value' (or maybe lets better call it meaning) as int 2
   "2" === 2 // is false, cause their types are different - a string is not an int

but I might be wrong because I don't know much about JavaScript ..

MillKaDe commented 7 years ago

to @dmitriyse

Ah, OK - now I understand a bit more .. But I am pretty sure that the language design team will not allow implicit cast to bool - neither in general nor limited to special places. Some explicitness (for example an operator) is needed to block the errors from C/C++ sneaking in ..

MillKaDe commented 7 years ago

Oh, and there is an aspect we have not discussed yet - should it be possible to overload that new check operator ?
Always ? Never ? Just for some types ? I think it should not be possible to overload it, especially not for reference types or primitive value types (e.g. the int types, bool, float, double, decimal)

But what about user defined value types, e.g. struct Point { int x; int y; } ? Or maybe the new check operator will be limited to refs and primitive value types, because it is considered to be too 'expensive' on non-primitive value types, e.g.

Point p; // a user defined struct / value type
if (?p) .. // what does that mean ? Should that be legal ?

Does it mean (?p.x || ?p.y) (= p.x != 0 || p.y != 0) ?

dmitriyse commented 7 years ago

@MillKaDe Thanks for you description. I forgot about "if (a=b)" like problems.

lachbaer commented 7 years ago

@MillKaDe @dmitriyse Exactly, we do not want implicit casting, for many, many good reasons! It is one of the main sources of errors in C/C++ and other languages!

This proposal is about a "is-non-default" operator. Maybe one day there will be an T operator default(T rhs) { }; that returns a new instance of any type (value/ref/Nullable). In that case ?obj will return true if it is either null (because ? somehow stands for a not-null-checking) or !obj.Equals(default(T)).

The correct equivalent of ?x so would be:

bool isNotNullOrDefaultQuestionmarkOperator<T>(T x) where T : class
    => ! ( ( x == null ) || ( x.Equals(default(T)) ) );

bool isNotNullOrDefaultQuestionmarkOperator<T>(T x) where T : struct
    =>  x.Equals(default(T));

bool isNotNullOrDefaultQuestionmarkOperator<T>(T? x) where T : struct
    =>  x.HasValue;
lachbaer commented 7 years ago

@MillKaDe

should it be possible to overload that new check operator ?

No, not in my understanding (see directly above). The 'overloading' would be done with a default-operator. This way ? will also work with the new record types.

MillKaDe commented 7 years ago

@lachbaer

About non-zero defaults: The default value default (T) is always a bunch of zero bits. The type T determines the type and the size / number of bits of that bunch of zero bits.

Why ? see this answer by Jon Skeet on stackoverflow.

A class / reference type C may have a parameterless default constructor, which can set the fields of the constructed object to any desired values. But the default value of the reference to that new object (the address/pointer in C/C++) itself is null (= 0).

In C# a struct / value type S is not allowed to have a default constructor (which has no parameters). The closest thing to a non-zero default value is :

struct S {
  public readonly MyDefaultValueForS = new S (42);
}

but default (S) must be all zero.

When an array of S S[..] is allocated, the runtime sets all bits in that array to zero. There are no calls of an default constructor of S for all the array elements. Instead the NET runtime does something like memset (s, 0, sizeof (array)) (from C/C++ runtime lib) on the whole array.

No matter how complex a type definition is, it always forms a tree-like graph. All fields of any type are always either

but any embedded struct follows these rules too, so sooner or later you will reach the leafes of the tree/graph and all leafs are either

Introducing some way to define non-zero default values - no matter how - would be a breaking change, which the language design team tries to avoid by all means. So I think there will never be a T operator default(T rhs) { ... }.

About overloading the new check operator: Because the default value always is (and always will be) zero bits, the check operator for type T can always (for all types) compare to default (T).

The new check operator should be predefined and not overloadable for:

The check operator would be most useful for references, and also useful for ints. It is not so useful for enums and non-integers. And it is not needed at all for bools, cause it would just return the bool itself - but maybe it make sense to define it anyway for symmetry reasons and fewer problems when used in generic types ?

I don't think it makes sense to allow the user to define the check operator for user defined structs, because its implementation would always have to be the very same:

public static bool operator ? (S s) {
  foreach (var field in struct.fields) {
    if (?field.value) return true; // found one field with non-default value
  }
  return false; // all fields have default value
}

However, this implementation could be a generated by the compiler.

As far as I know, the method System.Object.Equals (System.Object x) does some automatic memberwise comparison of all fields, when two structs of same type are compared with that Equals method. So a compiler generated standard implemention for operator '?' would be technically possible. But I don't think that it is desperately needed. But - again - it might make sense for symmetry and simpler use in generic types.

lachbaer commented 7 years ago

In Roslyn I (also) found

Debug.Assert(!isExtensionMethod || (receiverOpt != null));

Because ! is easily overseen (see #157), it should be either

Debug.Assert((isExtensionMethod == false) || (receiverOpt != null));

throughout the codebase.

Or we have another argument in favor of this proposal, because ? is not worse than !.

Debug.Assert(!isExtensionMethod || ?receiverOpt);

I like brevity - as long as it is understandable. So I still like the ? operator! ๐Ÿ˜

lachbaer commented 7 years ago

Meanwhile I do not think that there should be an implicit operator like this. For value types this could be achieved in a safe way by delacring custom 'struct' types, when really needed.

    public struct BoolInt
    {
        public int Value { get; set; }
        public BoolInt(int value) => Value = value;
        public static implicit operator bool(BoolInt rhs) => rhs.Value != 0;
        public static implicit operator BoolInt(bool rhs) => rhs ? -1 : 0;
        public static implicit operator int(BoolInt rhs) => rhs.Value;
        public static implicit operator BoolInt(int rhs) => new BoolInt(rhs);
        /* further operators */
    }
BoolInt v1 = 10;
while (v1) --v1;

I'd rather see an bool operator ? that gets called in the following scenarios

if (?secondItem) {
    var item = fistItem ?? secondItem;
    item?.Execute();
}

In all 3 scenarios the ?s check for 'null' in the first step and when the custom bool operator ? exists on the type (value or reference) calls it. Value types can't be 'null', instead the missing of the custom operator on the type makes all 3 statements produce a compile time error. This would have no impact on current code, even the IL is the same.

lachbaer commented 7 years ago

See also #545 for specific proposal of ?obj. Neither has support.

kennetherland commented 2 years ago

Why not just have the editor change if (obj) to if (obj != null) as autocorrect or AI assisted feature. Reverse, if (!obj) autocorrect to if (obj == null)