[C#] Conditional operator assignment, waarom werkt dit niet?

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • apNia
  • Registratie: Juli 2002
  • Laatst online: 22:12

apNia

Schreeuwen en Nibbits eten!

Topicstarter
Ik heb de volgende regel, waarbij het een gegeven is dat EventA en EventB IEvent implementeren.

C#:
1
IEvent e = success ? new EventA() : new EventB();


Dit levert een compile error op:
code:
1
error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between `EventA' and `EventB'


However, na wat proberen werkte dit wel.

C#:
1
IEvent e = success ? (IEvent)new EventA() : new EventB();


Kan iemand mij uitleggen waarom de compiler die cast verwacht?

Alvast bedankt :)

Acties:
  • +2 Henk 'm!

  • Cloud
  • Registratie: November 2001
  • Laatst online: 17-09 10:39

Cloud

FP ProMod

Ex-moderatie mobster

Volgens mij komt dat omdat de compiler niet naar het type kijkt van het object waarin je probeert op te slaan. Hij kijkt alleen naar de typen van de objecten waartussen hij moet kiezen.

En ik kan een hele dappere poging doen om dit uit te leggen maar dat lukt me toch niet zo goed als Eric Lippert:
We need to be able to work out the type of an expression without knowing what it is being assigned to. Type information flows out of an expression, not into an expression.

To work out the type of the conditional expression, we work out the type of the consequence and the alternative expressions, pick the more general of the two types, and that becomes the type of the conditional expression.

Never attribute to malice that which can be adequately explained by stupidity. - Robert J. Hanlon
60% of the time, it works all the time. - Brian Fantana


Acties:
  • 0 Henk 'm!

  • apNia
  • Registratie: Juli 2002
  • Laatst online: 22:12

apNia

Schreeuwen en Nibbits eten!

Topicstarter
Thanks Cloud! Ik ging er eigenlijk van uit het gewoon een shorthand was en dat de compiler er gewoon een if/else van maakte :)

Acties:
  • 0 Henk 'm!

  • Sendy
  • Registratie: September 2001
  • Niet online
bij een vergelijkbare if/else heb je toch hetzelfde probleem met het type?

Acties:
  • 0 Henk 'm!

  • YakuzA
  • Registratie: Maart 2001
  • Niet online

YakuzA

Wat denk je nou zelluf hey :X

Interessant, ik wist niet dat dit de reden was
Sendy schreef op donderdag 03 december 2015 @ 16:23:
bij een vergelijkbare if/else heb je toch hetzelfde probleem met het type?
(in het voorbeeld ga ik ervan uit dat zowel A als B de IEvent implementeren)
Nope, ter vergelijking een makkelijker voorbeeld:
code:
1
int? testInt = pulse == 0 ? null : pulse;

mag niet, maar
code:
1
2
3
4
5
6
7
int? testInt;
if(pulse == 0)
{ 
   testInt = null;
}else{ 
   testInt = pulse;
}

kan wel.

[ Voor 10% gewijzigd door YakuzA op 03-12-2015 16:45 ]

Death smiles at us all, all a man can do is smile back.
PSN


Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
YakuzA schreef op donderdag 03 december 2015 @ 16:44:
Nope, ter vergelijking een makkelijker voorbeeld:
Da's niet helemaal 'tzelfde; omdat er nu een null meespeelt die 'speciale regels' kent.

Het is heel logisch:

C#:
1
var x = true ? null : 1;

null kun je niet impliciet converteren naar een int of vice versa; exact wat de foutmelding aangeeft.
C#:
1
2
3
var x = true ? (int?)null : 1;
//of
var x = true ? null : (int?)1;

Beiden kun je impliciet converteren naar een nullable-int (int?).

Nu even een voorbeeld zonder null (komen we straks nog even op terug):
C#:
1
2
3
4
5
6
7
8
9
10
interface IFoo { }
class Foo : IFoo { }
class Bar : Foo { }
class Baz : IFoo { }

var x = true ? new Foo() : new Bar();  // Yay!
var y = true ? new Foo() : new Baz();  // Nope
var z1 = true ? (IFoo)new Foo() : new Baz();    // Yay!
var z2 = true ? new Foo() : (IFoo)new Baz();    // Yay!
var z3 = (IFoo)(true ? new Foo() : new Baz());  // Nope

Voor x werkt bovenstaand voorbeeld. Bar is namelijk ook een Foo (immers, Bar inherit van Foo).
Voor y werkt het niet omdat Baz geen Foo is.
Voor z1 en z2 werkt het omdat beiden IFoo implementeren en er dus voor beiden (impliciet) gecast kan worden naar IFoo.
Voor z3 werkt het niet omdat de expressie éérst überhaupt naar iets zal moeten evaluaten voordat het (daarna) gecast kan worden naar IFoo.

Lippert (eerder aangehaald) vat 't dan ook mooi samen. Wat er links van de assignment operator (=) staat boeit niet; je moet de expressie namelijk eerst (kunnen) evalueren voordat je kunt assignen en zolang je niet zeker weet welk type de expressie returned heeft 't ook geen nut om te gaan assignen (dus boeie wat 't type links van de assignment operator is; dat is op dat punt namelijk nog helemaal niet relevant). De "z3 case" demonstreert dat mooi; daar is de IFoo naar de rechterkant van de assignment operator gehaald maar omdat de compiler niet kan bepalen wat er uit de expressie (de ternary tussen de haken) komt weet de compiler ook niet of 'ie kan casten naar IFoo.
YakuzA schreef op donderdag 03 december 2015 @ 16:44:
maar
code:
1
2
3
4
5
6
7
int? testInt;
if(pulse == 0)
{ 
   testInt = null;
}else{ 
   testInt = pulse;
}

kan wel.
Omdat in beide gevallen de rechterkant van de assignment operator duidelijk is; je kunt een null of een int (pulse) aan testInt toekennen. Null is wat dat betreft misschien een beetje een raar geval; je zou eigenlijk zoiets "moeten" schrijven (als de compiler niet zo slim was als 'ie is ;) ):
C#:
1
2
3
4
5
6
7
8
9
int? testInt;
if (pulse == 0)
{
    testInt = (int?)null;
}
else
{
    testInt = pulse;
}

En pas je dat toe dan mag de ternary opeens wél:
C#:
1
2
3
int? testInt = pulse == 0 ? (int?)null : pulse;
//of...
int? testInt = pulse == 0 ? null : (int?)pulse;

Maar null is toch null?
Nou.. nee... bekijk dit maar eens:
C#:
1
2
3
4
5
6
int? testInt = true ? (Customer)null : 1;  //Nope
// Of het volgende:
var x = (Customer)null;
var y = (int?)null;
int? z1 = x;  // Nope
int? z2 = y;  // Yay

[ Voor 44% gewijzigd door RobIII op 03-12-2015 18:25 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • Kajel
  • Registratie: Oktober 2004
  • Laatst online: 29-07 12:04

Kajel

Development in Style

Er kan dan wel een "logische" uitleg voor gegeven zijn, ik vind het toch raar. Technisch gezien is het prima mogelijk voor een compiler om in dit scenario uit te vogelen wat het gemeenschappelijke type van de 2 opties is. Scala kan dat bv:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
scala> val c = true
c: Boolean = true

scala> trait Base
defined trait Base

scala> class A extends Base { }
defined class A

scala> class B extends Base { }
defined class B

scala> val x = if(c) new A() else new B()
x: Base = A@2d6a9952


M.a.w., ik zie dit gedrag als een tekortkoming van de C# compiler.

Acties:
  • 0 Henk 'm!

  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 21:27

Creepy

Tactical Espionage Splatterer

Kajel: of ik begrijp je verkeerd, of je hebt RobIII's post niet gelezen?
Voor x werkt bovenstaand voorbeeld. Bar is namelijk ook een Foo (immers, Bar inherit van Foo).

Voor z1 en z2 werkt het omdat beiden IFoo implementeren en er dus voor beiden (impliciet) gecast kan worden naar IFoo.

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


Acties:
  • 0 Henk 'm!

  • Raynman
  • Registratie: Augustus 2004
  • Laatst online: 00:45
RobIII legt de beperking van de compiler uit, niet waarom de compiler die beperking heeft. In C# moet je minstens een van de branches casten naar het gewenste resultaattype, in Kajels Scala-voorbeeld hoeft dat niet.

Lippert zegt in een blog post nog wel dat het niet altijd mogelijk is om een uniek type te kiezen, als er meerdere gemeenschappelijke interfaces en/of superclasses zijn. Op zich zou de compiler dan alleen in die ambigue gevallen een error kunnen geven en anders gewoon dat unieke type kiezen, maar de huidige werking is waarschijnlijk makkelijker voor de compiler en misschien ook wel simpeler/leesbaarder voor programmeurs omdat het resultaattype gelijk is aan een van de types aan de rechterkant.
Pagina: 1