Skip to content
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

Proposal: conditional with value type and null. #438

Closed
erik-kallen opened this issue Feb 12, 2015 · 17 comments
Closed

Proposal: conditional with value type and null. #438

erik-kallen opened this issue Feb 12, 2015 · 17 comments

Comments

@erik-kallen
Copy link

Currently (at least pre-roslyn), the code int? x = a ? 1 : null gives the error

CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between 'int' and '<null>'

and instead you have to type int? x = a ? 1 : (int?)null.

Can this be changed in a future version?

@khellang
Copy link
Member

Yes! 👍^💯

@tugberkugurlu
Copy link

👍

@jmezach
Copy link

jmezach commented Feb 12, 2015

Hell yeah! This has already been a major annoyance to me.

@rskopecek
Copy link

+1 that is very irritating.

@Bowman74
Copy link

Yes, that syntactic sugar would be very nice.

@axel-habermaier
Copy link
Contributor

supercat explained the potential problems with that -- still, it might be worth considering to allow it whenever the types could be determined unambiguously...

@MrJul
Copy link
Contributor

MrJul commented Feb 12, 2015

@axel-habermaier I believe this request is simply to special case a struct value of type T and null to return a Nullable<T>, not determining the best common type for two arbitrary expressions.

@omariom
Copy link

omariom commented Feb 12, 2015

@axel-habermaier, there should be no problems with value types.

It is interesting that in the following case it actually finds "implicit conversion between 'int' and '<nul>'" 😉

int val = 5;
if (val == null)

@mburbea
Copy link

mburbea commented Feb 13, 2015

@omariom , It's a conversion but not in the way you think.

The C# compiler is looking for an operator == that works for both an int and null. Untyped null is inferred to be object. So it simply is boxing val, and the condition is always false.

This too is also legal, var x = new[]{null,3}. The implicitly typed array is now of type object[], not of int?[]

@MrJul
Copy link
Contributor

MrJul commented Feb 13, 2015

@mburbea That's not what happens with ==, there's no boxing here.

C# specification, §7.3.7 Lifted operators

Lifted operators permit predefined and user-defined operators that operate on non-nullable value types to also be used with nullable forms of those types. Lifted operators are constructed from predefined and user-defined operators that meet certain requirements, as described in the following:
[...]
For the equality operators == !=
a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is bool. The lifted form is constructed by adding a single ? modifier to each operand type. The lifted operator considers two null values equal, and a null value unequal to any non-null value. If both operands are non-null, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.

int has a predefined operator==(int, int), so a lifted operator==(int?, int?) automatically exists which is chosen here.

A similar mechanism could be introduced for ?:.

@omariom
Copy link

omariom commented Feb 13, 2015

@MrJul, 👍

@mburbea
Copy link

mburbea commented Feb 13, 2015

Err no. @MrJul that is not correct.
The compiler makes no assumption that null here is an int?. The il will translate to::

ldc.i3
stloc.0
ldloc.0
box Int32
ldnull
brfalse (label)

You can get your desired behavior of lifted operators, by declaring val as a int? or by specifying that null here is a int?
e.g.

int? val = 3;
if(val == null) // works! no boxing.
//or
int val = 3;
if(val == (int?)null) // again no boxing. 

@MrJul
Copy link
Contributor

MrJul commented Feb 13, 2015

No such IL will ever be generated since the compiler is smart enough to see the branch is always false. Still, I disagree with you, it's the lifted operator ==(int,int) that is used, as seen on a simple expression, not a boxed value. Getting the symbol info from Roslyn also displays that operator.

Anyways, whether its boxing or lifting, our argument doesn't add much to the original proposal. I was simply suggesting to use a similar behavior as lifted operators.

I propose to amend the spec (§7.14 conditional operator). In bold:

  • If x has type X and y has type Y then
    • If an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
    • If an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
    • Otherwise, no expression type can be determined, and a compile-time error occurs.
  • If only one of x and y has a type, and both x and y are implicitly convertible to that type, then that is the type of the conditional expression.
  • If x has a non-nullable type X and y is the null literal, then the type of the conditional expression is a nullable value type whose underlying type is X.
  • If x is the null literal and y has a non-nullable value type Y, then the type of the conditional expression is a nullable value type whose underlying type is Y.
  • Otherwise, no expression type can be determined, and a compile-time error occurs.

@GSPP
Copy link

GSPP commented Jun 12, 2016

@MrJul in your spec we could even replace "literal" with "constant value null". That way this would work with more esoteric code like (1 == 1 ? null : null) ? 1 : null and const string n = null; b ? 1 : n;. I see no downside to doing that. The C# language already has provisions to treat compiletime constant values specially (e.g. Enum E { X = 1 }; E e = 0; works).

@realbart
Copy link

I think using the conditional operator in conjunction with structs can be unambiguously defined. C# is already aware of nullable values: it even has a special syntax, with the question mark.

You should be able to compile this code:

 public static Nullable<T> Iif<T>(bool condition, T value)
     where T : struct
 {
     return condition ? value : null;
 }

to roughly the same cil that this code compiles to:

 public static Nullable<T> Iif<T>(bool condition, T value)
     where T : struct
 {
     const Nullable<T> nullValue = null;
     return condition ? value : nullValue;
 }

@Richiban
Copy link

I'm going to largely copy my comment from #11886 because it shows one of the surprises that this results in, i.e. that the following is legal:

            if (condition)
                return 1;
            else
                return null;            

but this is not:

            return condition ? 1 : null;

As programmers learn C# they go for the ternary operators an awful lot and it's pretty difficult to try and explain why the first example works and the other doesn't.

@gafter
Copy link
Member

gafter commented Sep 27, 2017

We are now taking language feature discussion in other repositories:

Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https://github.com/dotnet/csharplang/issues, and there is a draft spec at https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https://github.com/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952.

In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead.

Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you.

If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue.

Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to #18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo.


This proposal is being tracked at dotnet/csharplang#33

@gafter gafter closed this as completed Sep 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests