C# Syntax – Is ‘Downcast If Block’ a Reasonable Language Feature?

clanguage-featuressyntaxtype casting

Consider the following "if cast":

class A     { public void f1() { ... } }
class B : A { public void f2() { ... } }

A a = foo(); // might return A or B

if ( a is B ) {
    //  Inside block, implicitly "redeclare" a as type B
    //  It's B, go for it
    a.f2();
    //  Wouldn't compile.
    a = new A();
}

We don't enter the block unless a is a B, therefore within the block, the compiler treats a exactly as if it had been declared as type B. You could assign null to it in the block if it's a reference type, but it would be a compile-time error to assign new A() to it, for example (thanks @ThomasEding for bringing up that question).

Anything that could break that assumption that a is B, could just as easily break the following legal C# as well:

if ( a is B ) {
    B b = a as B;

    b.f2();
}

The former looks to me like syntactic sugar for the latter.

Even if this feature makes sense, I'm sure Anders Hjelberg's lads have more useful features to implement. I'm just wondering if it's a worse idea than I think it is.

UPDATE A serious objection would be that the feature as described adds semantics without adding syntax: From one version of the language to the next, the meaning of the above block would change significantly. It would be more tolerable with a new keyword. But then you'd be adding a new keyword.

Update

A more redable version of this feature was implemented in C#7:

object object2 = new List<Test>();

if (object2 is Dictionary<String, int> foo)
{
    Console.WriteLine(foo.Keys);
}

Best Answer

Let's assume features start with 0 points, rather than with -100 points (due to costs associated with implementation, design, etc.).

This feature strikes me as confusing. I've already declared that a is an A. If I want to know the compile-time type of a, I only have to look in one place, at the declaration. While I acknowledge that the implementation is relatively simple (use the code substitution you describe in your question), this also argues in favor of not implementing the feature; it's easy for coders to do it, too.

The truly horrifying thing is that the type of a variable can magically change immediately after a }. C# very deliberately prevents this from happening (see Simple names are not so simple). So, I absolutely hate any version of this feature that fails to introduce a new variable during the cast.

If you find yourself constantly casting your types so as to access members of derived classes, it makes me suspect you are misusing inheritance. Personally, usually in code where I'm using such a conditional, I'm going to break out of my function if the cast fails. So, my code would look more like this:

B myB = myA as B;
if (myB == null) return;

Which would, under some variant of your proposal, become this:

if (!(myA is B)) return;

or (introducing a new name)

if (!(myA to B myB)) return;

or, using declarative expressions (this is a variant on Jimmy Hoffa's answer)

if (!a.To(out B myB)) return;

With the exception of the approach using declarative expressions, all of these features require learning new C# syntax for a feature that is not at all painful to deal with. The declarative expression approach is nice in that it is the general case of a specific feature; i.e. allowing coders to make use of an assignment and conditional using a single expression.

The only cost to the declarative expression approach is that it won't work universally; it only works if your special To method is implemented and visible in your current scope. However, that's a price I'm willing to pay to avoid adding yet more syntax to the C# language; I won't bother using such an extension method unless I'm dealing with code which constantly requires it (and probably not even then).

In summary: This feature isn't needed quite often enough to justify adding new syntax to the language. However, the more general feature, declarative expressions, may be worth the cost (which is why Microsoft is probably going to pay it). It not only handles this case, but also handles the annoying two-statement TryParse case (and, like your proposal, makes for clean scope; the type is only in scope on the inside of the conditional).

Update: Declarative Expressions were cancelled.

Update 2: C# 7.0 is adding "out variables". This might be enough for my final example (if (!a.To(out B myB)) return;). C# 7.0 is also adding "Is-expressions with patterns", which allows, if (!(o is int i)) return;. This is basically the feature you asked for.