2010-12-12 4 views
5

I just encountered this (made up code to demonstrate the "problem"):The type of the conditional expression can not be determined?

public ICollection<string> CreateCollection(int x) 
{ 
    ICollection<string> collection = x == 0 
            ? new List<string>() 
            : new LinkedList<string>(); 
    return collection; 
} 

The compiler complains:

Fehler CS0173: Der Typ des bedingten Ausdrucks kann nicht bestimmt werden, weil keine implizite Konvertierung zwischen "System.Collections.Generic.List" und "System.Collections.Generic.LinkedList" erfolgt.

Which translates roughly to:

The type of the conditional operator can not be determined, because there is no implicit conversion between List and LinkedList.

I can see why the compiler complains, but hey, come on. It is trying to play stupid. I can see that both expressions are not of the same type but have one common ancestor and as a bonus the type of the left side is also a common ancestor. I am sure the compiler can see it too. I could understand the error if the left side was declared as var .

What am I missing here?

Edit:

I am accepting James Gaunt's explanation. Maybe Just to make it clear. I can read the compiler spec just fine. I wanted to understand why. Why did someone make the decision to write the spec that way. There must be a reason behind that design. According to James the design principle is 'no surprises'. Also CodeInChaos explains what surprises you might encounter if the compiler would try to deduce the type from common ancestors.

+1

Versuchen Sie, eine explizite Umwandlung an ICollection an einem der bedingten Ergebnisse zu kleben. –

+0

Ich weiß, wie ich es umgehen kann, ich will es nur verstehen. – EricSchaefer

+0

was wäre wenn ...der Compiler würde einen anonymen Typ erstellen, der (A) alle gängigen Schnittstellen implementieren würde (B) als Wrapper für das ausgewählte Objekt fungieren würde? – m0sa

Antwort

9

The expression (a ? b : c) has to resolve to a type. The type will be either the type of b or c. If these are different (and there isn't an implicit conversion from one to the other) the compiler doesn't know which type this is at compile time.

You may say it should deduce that there is common root type, but there is always a common root type (e.g. Object).

In general the C# compiler will not attempt to guess what you mean. If you want to use the common root type then cast b and c to that type.

Diese Art von Logik läuft im gesamten Design von C#, es ist gelegentlich ein bisschen nervig, aber weit öfter stoppt es Sie Fehler machen.

+0

Gut mit 'var' versucht der Compiler zu erraten ... – EricSchaefer

+9

Nein, rät nicht. var kann nur verwendet werden, wenn der RHS-Typ bekannt ist. var bewirkt nicht, dass der Compiler etwas anderes tut - es spart nur das Tippen. –

+3

Diese Art von Frage taucht regelmäßig auf Eric Lipperts Blog auf. Am Ende des Tages ist es ein Kern-Design-Prinzip von C#, manchmal bezeichnet er es als "keine Überraschungen". –

2

The left-hand side isn't taken into consideration at all when determining the type of the right-hand side.

It's only when the compiler has independently determined the right-hand side's type that it checks for assignment compatibility with the left-hand side.

As for your claim that both types "have one common ancestor" : would that be ICollection , IEnumerable , ICollection<T> , IEnumerable<T> or Object ? What heuristic should the compiler use to unambiguously determine which type you want? The compiler is simply asking you to specify, rather than trying to guess your intention.

+0

Aber immer noch haben beide Ausdrücke auf der rechten Seite einen gemeinsamen Vorfahren (typenweise). – EricSchaefer

+0

sie haben viele gemeinsame Vorfahren, die den Compiler einen Fehler werfen lassen ... würden Sie erwarten, dass dies auch funktioniert? (var obj = true? 1: "0"), welcher Typ wäre obj jetzt? nach Ihrer Logik wäre es vom Typ Objekt, aber das ist ziemlich weit hergeholt. –

+0

Nun, zumindest könnte der Compiler versuchen, schlau zu sein. Da klagt ein Kunde immer: "Warum muss ich das machen, lass den Computer das machen ..." ;-) – EricSchaefer

2

Es ist einfach die Definition von ?:, gleiche Typen zu erfordern. natürlich
Sie könnten

? (ICollection<string>) new List<string>() 
: (ICollection<string>) new LinkedList<string>(); 

verwenden oder nur eine if/else verwenden.

Gemäß der C# Referenz, § 14.13,

[Gegeben] Ein bedingter Ausdruck der Form b ? x : y

  • Wenn X und Y der gleiche Typ ist, dann ist dies die Art der bedingten Ausdruck.
  • Andernfalls, wenn eine implizite Konvertierung (§13.1) von X nach Y, aber nicht von Y nach X existiert, dann ist Y der Typ der bedingte Ausdruck.
  • Andernfalls, wenn eine implizite Konvertierung (§13.1) von Y nach X, aber nicht von X nach Y existiert, dann ist X der Typ der bedingte Ausdruck.
  • Andernfalls kann kein Ausdruckstyp bestimmt werden, und ein Fehler bei der Kompilierung tritt auf.

In Ihrem Fall haben beide X und Y eine Umwandlung in Z, aber das hilft nicht. Es ist ein allgemeiner Grundsatz in der Sprache, dass der Compiler bei der Anwendung der Regeln nicht einmal auf die Zielvariable schaut. Einfaches Beispiel: double a = 7/2; // a becomes 3.0

So, nachdem dieses ich etwas lesen würde es genügen, nur der Ergebnisse zu ICollection<string> zu werfen. Das habe ich nicht getestet.

+0

Ich verstehe das. Ich möchte wissen warum. – EricSchaefer

+0

@EricSch, OK, ich habe die Regeln zitiert. –

+0

Ich kann die Regeln lesen. Nochmal: Warum? Es ist eine Designentscheidung, die aus einem bestimmten Grund getroffen wurde. Ich wollte den Grund wissen ... – EricSchaefer

4

Aufgrund von Schnittstellen können sie mehrere verschiedene gemeinsame Vorfahren haben.

Man könnte eine Anforderung hinzufügen, dass es nur automatisch konvertiert, wenn der Vorgänger unzweideutig ist. Aber das Hinzufügen zusätzlicher Schnittstellen, die eine Klasse implementiert, wird plötzlich zu einer bahnbrechenden Änderung. Und das ist vielleicht nicht wünschenswert.

Nehmen wir zum Beispiel an, Sie machen diese Typen implementieren ISerializeable. Dies sollte das Verhalten Ihres Codes nicht ändern, aber wenn Sie dieses Casting für eine allgemeine Schnittstelle unterstützen würden, würde dies der Fall sein.

edit: Ich dachte, ein bisschen mehr über sie und bemerkte, dass diese Funktion bereits hat genau das gleiche Problem:

T MyFunc<T>(T left,T right) 

Und dieser Code nicht kompiliert:

, weil es kann‘ t entscheiden, welcher Typ als Typ-Parameter verwendet werden soll T. Das Verhalten des Operators?: Entspricht der Überladungsauflösung.

+0

Jetzt macht das Sinn (besonders der Teil über die spätere Hinzufügung neuer Vorfahren). – EricSchaefer