Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: null evaluation operator to boolean #196

Closed
lachbaer opened this issue Feb 27, 2017 · 47 comments
Closed

Discussion: null evaluation operator to boolean #196

lachbaer opened this issue Feb 27, 2017 · 47 comments

Comments

@lachbaer
Copy link
Contributor

lachbaer commented Feb 27, 2017

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
Copy link
Contributor

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
Copy link
Contributor

svick commented Feb 27, 2017

@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
Copy link
Contributor

jnm2 commented Feb 27, 2017

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

@lachbaer
Copy link
Contributor Author

lachbaer commented Feb 27, 2017

@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
Copy link
Contributor Author

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
Copy link

MillKaDe commented Feb 27, 2017

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
Copy link

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
Copy link

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
Copy link
Contributor Author

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
Copy link
Contributor

jnm2 commented Feb 27, 2017

@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
Copy link
Contributor Author

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
Copy link
Contributor

@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
Copy link
Contributor Author

@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
Copy link
Contributor

@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
Copy link
Contributor Author

@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
Copy link
Contributor

@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
Copy link
Contributor Author

@HaloFour
No it wouldnt print yay`. 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
Copy link

MillKaDe commented Feb 28, 2017

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

    • either explicitely, e.g. null, 0, ...
    • or implicitely: default (X)

    because otherwise we could just close the case and continue to compare x to the explicit or implicit default value, e.g.

    • x != null
    • x == 0
    • x != default(X)
  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:

    • bool
    • one of non-integer number types: float, double and maybe decimal
    • one of the eight integer types: [u]int, [u]long, [u]short or [s]byte
    • enums are just ints where some values have names
    • some class/object type, which means x is a reference
    • some struct/value type, which means its default value default(X) is a bunch of zero bits (remember that for initialization of value types no default parameterless constructor is called, instead the CLR just zeroes the memory)
  5. Comparing ..

    • a bool x to true or false explicitely is just for bloody newbies - x is already bool
    • a non-integer number to exactly 0.0 is not terribly smart either, cause x might be very close to zero but still not exactly equal
    • an integer x to 0 might make sense, e.g. SomeCollection.Count != 0 or SomeArray.Length != 0
    • an enum x: in most cases, enums should only be compared to their named values
    • a reference x: either we compare it to null or to some other reference y (which might be null or not null).

    Looks like the suggested new check operator only makes sense for references and ints - and maybe for other struct/value types.

  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:

    • in a condition of an if statement: if (x != null) ..
    • in a condition of a while loop: while (x != null) ..
    • in a condition of a for loop: for (X x = list.head; x != null; x = x.next) ..
    • in a ternary expression: var z = x != null ? a : b;
    • in an assignment var z = x != null;
    • as a function argument var z = func (x != null).

    Note that sometimes the check is surrounded by parenthesis () and sometimes not.
    That means pulling the check operator out of those parenthesis

    if ?(x) instead of if (?x)

    is sometimes not possible, because sometimes there are no parenthesis.

  8. Conclusion:

    • We dont want to adopt automatic conversion to bool, because we want to avoid all the problems and bugs enabled by automatic conversion.
    • Some of us would like to use a shorter syntax similar to C/C++ sometimes.
    • Explicit use of an unary prefix operator avoids the problems of automatic conversion.
    • The check operator would be somewhat similar to the null conditional operator '?.' in reference?.member. That points to using question mark '?' as check operator.
    • C# has its roots in C/C++. In C++, the unary prefix negation operator '!' has a precise meaning when applied to non-bools. I think we should not define the exact opposite meaning in C#. That means '!' cant be the check operator.
    • Since the check operator produces a bool result, all bool operators could be applied to the result, e.g.
      • ?x means x != null
      • !?x means !(?x) means !(x != null) means x == null
      • !?x is not one new operator '!?', but the combination of the suggested new check operator '?' and the existing negation operator '!'.
  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
Copy link
Contributor Author

lachbaer commented Mar 2, 2017

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
Copy link

MgSam commented Mar 2, 2017

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

@MillKaDe
Copy link

MillKaDe commented Mar 2, 2017

@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
Copy link
Contributor Author

lachbaer commented Mar 3, 2017

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
Copy link
Contributor Author

lachbaer commented Mar 3, 2017

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
Copy link

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
Copy link
Member

yaakov-h commented Mar 4, 2017

@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
Copy link
Contributor Author

lachbaer commented Mar 4, 2017

@DavidArno 👍 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
Copy link
Member

yaakov-h commented Mar 4, 2017

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
Copy link
Contributor Author

lachbaer commented Mar 4, 2017

@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
Copy link

MillKaDe commented Mar 4, 2017

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

    • t.HasValue is false
    • t.Value is default (T)
  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:

    • if t is null / has no value, t? resolves to null, and therefore t?.Value resolves to null.
    • if t is not null / has a value, t? resolves to t, and therefore t?.Value resolves to t.Value.

    In the first case (t is null) we now have:

    if (?(null)) ...;

    which resolves to false. Is that correct ?

    Does t have a value and is that value different from the default value ?
    No, because t has no value.

    Looks like this result is correct.

    In the second case (t is not null) we now have:

    if (?(t.Value)) ...;

    which is equivalent to t.Value != default (T).
    Looks like that result is correct, too.

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

    • either I should know the operator precedence table backwards without errors even when completely drunk
    • or I should use some (...) until the meaning is crystal clear.

@MillKaDe
Copy link

MillKaDe commented Mar 5, 2017

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
Copy link
Contributor Author

lachbaer commented Mar 5, 2017

@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
Copy link

MillKaDe commented Mar 5, 2017

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
Copy link

dmitriyse commented Mar 5, 2017

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
Copy link

MillKaDe commented Mar 5, 2017

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
Copy link
Contributor Author

lachbaer commented Mar 5, 2017

@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
Copy link

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
Copy link

MillKaDe commented Mar 5, 2017

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
Copy link

MillKaDe commented Mar 5, 2017

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
Copy link

MillKaDe commented Mar 5, 2017

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
Copy link

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

@lachbaer
Copy link
Contributor Author

lachbaer commented Mar 5, 2017

@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
Copy link
Contributor Author

lachbaer commented Mar 5, 2017

@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
Copy link

MillKaDe commented Mar 5, 2017

@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

  • one of the predefined primitive value types (ints, bool, float/double/decimal),
  • a reference (pointer) to another object / class
  • an embedded struct

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

  • one of the predefined primitive value types (ints, bool, float/double/decimal) - all with default value zero
  • a reference (pointer) to another object / class - with default value zero.

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:

  • references
  • all integer types: [u]int, [u]long, [u]short, [s]byte and [U]IntPtr (aka native int)
  • all enums, which are just ints with named values
  • all non-integer number types: float, double, decimal
  • bool

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

lachbaer commented May 8, 2017

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

@kennetherland
Copy link

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)

@jnm2 jnm2 converted this issue into discussion #6276 Jul 14, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests