-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Champion: Target-typed switch expression (16.3, Core 3) #2389
Comments
I really like this. But i definitely beg of you to do the same for |
Is there any reason not to do this generally, for all expressions? |
If I remember correctly this feature was already requested for bool b;
var i = b ? 0 : null; // i become Nullable<int> |
I like this idea, as long as it works with interfaces, not just inherited types: interface I
{
}
class B : I
{
}
class C : I
{
}
class D
{
void M()
{
I a = null;
a = a switch
{
B b => b,
C c => c,
_ => throw new System.Exception(),
};
}
} |
Would this work the same in a return statement, using the return-type of
the method as the target type?
…On Thu, 4 Apr 2019 at 08:45, David Arno ***@***.***> wrote:
I like this idea, as long as it works with interfaces, not just inherited
types:
interface I
{
}
class B : I
{
}
class C : I
{
}
class D
{
void M()
{
I a = null;
a = a switch
{
B b => b,
C c => c,
_ => throw new System.Exception(),
};
}
}
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#2389 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ANBBwblWPCbRIV_lZowpTgHVYlg5C8Osks5vda2HgaJpZM4cbr7a>
.
|
An interesting point related to this was raised in #2387. To clarify, @gafter, when you talk of "target typed", are you referring to the type of the target of the expression? For example, int? x = y switch { … } would this mean that if that switch were a "target typed switch", then the type of class D
{
void M()
{
A a = null;
var a2 = a switch
{
B b => b,
C c => c,
_ => null,
};
}
} where the type of the switch is inferred from a common type shared by |
Also useful in null-coalescing operator (from #877) SyntaxNode n = expressionSyntax ?? statementSyntax; Casting either of operands in any of these should be unnecessary. |
@alrz good point. I think the general idea of like to see investigated is that if we're in a position where there is some Target-type, and if you're using one of the 'branchy' expressions (i.e. switch-exoression, Both |
@DavidArno That example would not work. Either there has to be a common type (as currently specified), or there has to be a target type. That example has neither. @spydacarnage Yes, this would work in a return statement, on the right-hand-side of an assignment statement, as an argument to a method call (even if there are different overloads with different parameter types), and even for a switch expression that is inside another switch expression. There are lots of places where there is a target type. |
@gafter That's excellent. I can't wait :-) |
That's a great shame. I'm struggling to see why people are getting excited over it therefore as surely it only works in inheritance scenarios and so seems of limited appeal. EDIT Actually I partially take that back. It does have a small benefit in that: var x = a switch
{
B b => (I)b,
C c => c,
_ => throw new System.Exception(),
}; can be rewritten as: var x = (I)(a switch
{
B b => b,
C c => c,
_ => throw new System.Exception(),
}); There's still a cast in there to make it "target typed" but it's in a less obscure location. And if it does work with returns, that could be rewritten to this, I suppose: var x = SomeSwitch(a);
I SomeSwitch(I a) => a switch
{
B b => b,
C c => c,
_ => throw new System.Exception(),
}; So it's a very slight improvement on what we currently have. |
@DavidArno One of the main uses I can see is in MVC actions (this currently does not work without casting one of the ?: branches as IActionResult):
|
Sure, there are use-cases where the target type can be determined and used. And this feature would be useful for that. But this doesn't address the underlying problem for me, namely that interfaces can't be used as the "common type (as currently specified)" (as per @gafter's comment). It masks the problem instead of fixing it. |
We may be reading the same thing differently. I assumed that @gafter's response of "that won't work" was directed to your "var a2 = a switch ..."example, because neither the switch block had a common type, and var is not a valid target type for the purposes of determining the result of the switch. On the other hand, "IExample a2 = a switch ..." has a defined target type, and as both branches of the switch block could be represented as an IExample, it would work. Or, at least, that's how I understood the proposal. I would certainly be less interested if it didn't work for interfaces, too... |
I'm not sure that's possible. Here's the issue. I would like this to work byte M(bool b) => b switch { false => 0, true => 1 }; Since the switch expression is new, we can make it target-typed (i.e. have a conversion-from-expression that is defined even if the switch expression has a natural common type of its arms) to make that work. Note that the originally proposed spec above would not make this work: you would be required to cast all arms of this switch to byte M(bool b) => b ? 1 : 0; because that conditional expression has type void M(byte b) {}
void M(long l) {}
void Test(bool b) => M(b ? 1 : 0); This calls So I'm thinking it may make more sense to do it for the switch expression only. |
@gafter |
I think the idea here is that we use the target type only if the compiler could not determine the type of :? or ?? based on current rules (which is an error at the moment). |
Agreed, only change the behavior where it would be a compiler error today. |
In fact, I think we should do the same for switch expressions as well (try to find a common type, if failed, then do the target-typing). Consider this: AbstractBase x = e switch { P1 => new Derived(1, 2), P2 => new(1, 2) }; if we target-type the whole expression from get-go this would fail to compile, but the compiler could infer This also makes it consistent with :?, ?? if we ever implement target-typing for those. edit: this is actually what is being proposed:
So if I'm not missing something, @gafter's example above would infer |
Hey neal, sorry if i was unclear here. My design would not be "if the conditional expression's type cannot be converted to the target type, then try to target-type the branches". Instead, it would be: "if no common type can be found between teh conditional branches and there is a target-type for the conditional expressoin, then attempt to target type the branches". Does that make more sense? Or would there still be problems with that approach? Thanks! |
Not so. There is not a conversion-from-expression from this switch expression to the target type by virtue of the proposed switch conversion, but there is a conversion from the switch's natural type to the target type, and that would therefore be used. @CyrusNajmabadi That would work for the conditional expression, but to fix this error byte M(bool b) => b switch { false => 0, true => 1 }; // error: no implicit conversion from int to byte you'd have to write this byte M(bool b) => b switch { false => (byte)0, true => (byte)1 }; Of course I could have made it much worse, but this is a problem I would like to solve before putting it into the language that way. Mads suggests possibly treating |
I'm happy if we just make |
I've updated the proposal to make this work: byte M(bool b) => b switch { false => 0, true => 1 }; |
I think Union/Or types proposed in my #399 will be useful to determine the "common type" and then targeting another type, be it an interface or something else. Actually the Union type would be the "common type". Something similar was suggested by @HaloFour in #33 (comment) The way it works is simple- var result = x switch { true => new A(), false => new B() };
// the type of result here will be- A|B If all participating types of an Union/Or type individualy inplements an interface (or their "most common type" in inheritance tree inplements that interface), target typing to that interface is possible, otherwise error- interface IAB { }
class A : IAB { }
class B : IAB { }
// the expression
IAB result = x switch { true => new A(), false => new B() }; |
I see the same thing with delegates as well. private Action Test(string name) => name switch {
"John" => () => Console.WriteLine("Hello John"),
"Jane" => () => Console.WriteLine("Hello Jane"),
_ => () => Console.WriteLine("Who are you?")
}; This gives me the compiler error "No best type was found for the switch expression." If I make one of them something like this () => new Action(() => Console.WriteLine("Hello John"), then everything is fine. It seems like the compiler should be able to deduce that I'm only returning actions with no parameters. |
This was approved today in the LDM. We also approved the change to the conditional expression, and that is being treated as a separate feature and tracked at #2460. This change to the switch expression must be done in C# 8 (because it would be a breaking change to do it later). The change to the conditional expression is approved for C# 8 but could be done later if we are pressed for time. |
Thanks @gafter You rock! |
As a practical matter we are pressed for time. We will get this change into C# 8 but we did not have time to make the corresponding change to conditional expressions (#2460) for C# 8. |
An implementation for this is now in Roslyn master. |
No. We are tracking that in #33 ;) |
@333fred asked me
My initial reaction was
However, my answer was not satisfactory to either @333fred or myself.
This is a proposal to "do that".
Proposal
Currently, a switch expression is specified to have a type that is the inferred common type of the expressions in the switch arms. It is (currently) required that there be such a common type.
Instead, I propose
T
if there is an implicit conversion from every arm's value expression toT
.T
be the type of the switch expression, either the target type of the switch expression conversion, or if it was not subject to such a conversion the expression's natural type. It is an error ifT
does not exist. It is an error if a switch arm's expression cannot be implicitly converted toT
.The text was updated successfully, but these errors were encountered: