views:

191

answers:

7

Here's what I want to do:

XmlWriter writer = XmlWriter.Create(
    (string.IsNullOrEmpty(outfile) ? Console.Out : outfile)
);

This does not compile, however, giving the error "Type of conditional expression cannot be determined because there is no implicit conversion between 'System.IO.TextWriter' and 'string'". The above code is a simplification of the following:

XmlWriter writer;

if (string.IsNullOrEmpty(outfile))
{
    writer = XmlWriter.Create(Console.Out); // Constructor takes TextWriter
}
else
{
    writer = XmlWriter.Create(outfile); // Constructor takes string
}

These two calls to Create are perfectly valid, and this compiles. Is there a way to make this more compact, like I was trying to do with my inline test?

It doesn't make sense to me that what I want doesn't work. Mentally thinking through this, it seems like the compiler would evaluate string.IsNullOrEmpty(outfile) to determine which case to take:

  • If the condition were true, it would go with Console.Out, and then see that it needs to polymorphically choose the version of XmlWriter.Create that takes a TextWriter.
  • If the condition were false, it would go with outfile, and then see that it needs to polymorphically choose the version of XmlWriter.Create that takes a string.

Has programming in ML warped my brain?

+18  A: 

The reason you can't do that is because the compiler must choose which overload of Create to use at compile time - your approach would require it to be done at runtime. The shortest you can make it is probably:

XmlWriter writer = String.IsNullOrEmpty(outfile)
    ? XmlWriter.Create(Console.Out)
    : XmlWriter.Create(outfile);
Lee
+2  A: 

The problem is that you can't determine at compile time what

(string.IsNullOrEmpty(outfile) ? Console.Out : outfile)

should return. Is it going to be a string or is it going to be a TextWriter? That can only be determined at run time, hence the compile error because the ? operator has to be resolved at compile time.

The best you can get out of it would probably be:

XmlWriter writer = string.IsNullOrEmpty(outfile)
    ? XmlWriter.Create(Console.Out)
    : XmlWriter.Create(outfile);
Joseph
+1  A: 

No, you have to make two separate calls, as they are two separate constructors.

Which overload to call is determined at compile time, so you can't select a data type at runtime to call different overloads.

Also, the conditional operator can only return a single data type, you can't have it return different data types depending on the choise.

Guffa
+3  A: 

The C# compiler chooses a method to execute statically during compilation. The IL that gets generated when you compile is a reference to a specific method. The polymorphism part comes in at runtime when it chooses what implementation of that specific function to execute.

Your ?: statement is evaluated at runtime and as such the compiler can't figure out which method to execute.

Change to this and it'll work.

XmlWriter writer = string.IsNullOrEmpty(outfile) ? 
    XmlWriter.Create(Console.Out) :
    XmlWriter.Create(outfile);
Adam Sills
+1  A: 

C# is statically typed all the polymorphism magic is happening compile time. And the type of your conditional expression is not known at the compile time.

mfeingold
The compiler chooses the class/method name and arguments at compile time, but polymorphically chooses the implementation of that method via the callvirt IL instruction at runtime.
Adam Sills
+2  A: 

A couple of things are happening here.

First, the "exception" is occurring because of The Ternary Operator (tm), not because of where you're using it. The problem is because you've got a single expression that is trying to return two different types that can't be resolved to a single, common base type (other than object).

Further, your constructor overloads probably take two completely different types that aren't related in any way whatsoever. The C# compiler is very, very smart, but it's not quite that smart.

Mike Hofer
+7  A: 

Everyone seems to be suggesting the following:

XmlWriter writer = String.IsNullOrEmpty(outfile)
    ? XmlWriter.Create(Console.Out)
    : XmlWriter.Create(outfile);

However, this is also doable:

XmlWriter writer = XmlWriter.Create(string.IsNullOrEmpty(outfile)
    ? Console.Out : new StreamWriter(outfile));

The latter is closer to your original attempt and, IMO, more compact.

Chris