0

Recently, I was rewriting some old code, especially shrink too long functions (reduce overhead, extend readability etc..). Thereby I stumpled about something that actually works:

Class definition

public abstract class BaseClass {
    public string BaseProperty { get; set; }
}

public sealed class ClassA : BaseClass {
    public string PropertyA { get; set; }
}

public sealed class ClassB : BaseClass {
    public string PropertyB { get; set; }
}

Execution

ClassA a = null;
ClassB b = new ClassB();

(a == null ? (BaseClass)b : a).BaseProperty = "What would Jon Skeet think about this?";
Console.WriteLine(b.BaseProperty);

Output

What would Jon Skeet think about this?

Can someone explain to me how this works? What is this sorcery? Of course I can handle both instances the same way as they share a common base class, but I'm only casting one of them to the base class and I'm doing a property assignment based on a condition. Also, is this considered as a good practice or are there any significant downsides I just can't see?

Atrotygma
  • 1,133
  • 3
  • 17
  • 31
  • a does equal null so it calls b.BaseProperty, does look like a really dodgy way to write code though – Sayse Jul 28 '13 at 21:28

3 Answers3

1

You are using the conditional operator(?) similiar to this more redable if:

if (a == null)
    b.BaseProperty = "...";
else
    a.BaseProperty = "...";
Console.WriteLine(b.BaseProperty);

The cast in this code

(a == null ? (BaseClass)b : a).BaseProperty = "..."

is just needed because the conditional operator needs to know the type. The type of the conditional expression must be consistent for both.

So in my opinion it's bad practise since it's not clear what you're doing.


Why the cast to BaseClass is required: the relevant section of the C# 5.0 spec is 7.14, the conditional operator:

The second and third operands, x and y, of the ?: operator control the type of the conditional expression.

  • If x has type X and y has type Y then
    • If an implicit conversion (§6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
    • If an implicit conversion (§6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
  • Otherwise, no expression type can be determined, and a compile-time error occurs.

So since there is no implicit conversion between ClassA and ClassB (they have just the same base class), you have to cast it mnaually.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1
 (a == null ? (BaseClass)b : a)

is

 BaseClass x = null;
 if(a == null)
     x = (BaseClass)b;
 else
     x = (BaseClass)a;    // Here is implicit cast to the type of the second argument of ? operator
 return x;
dmay
  • 1,340
  • 8
  • 23
  • Although this doesn't because there is no implicit coversion between B and A: `((BaseClass)(a == null ? b : a)).PropertyBase = "hello";` – Atrotygma Jul 28 '13 at 21:37
  • Check out `?` documentation, I don't remember how it sais exactly, but idea is the return type of the whole operator is defined by the first value argument. In your example it's `(BaseClass)b` which is, obviously, `BaseClass` and `a` can be easly casted to it implicitly. – dmay Jul 28 '13 at 21:50
  • Or just check Tim Schmelter's comment, he explained it better. – dmay Jul 28 '13 at 21:53
  • Actually, Jon Skeet gave me a very good explanation: `You need the cast on one of the operands, because the overall type of the expression has to be either the type of the second operand or the type of the third operand... but neither ClassA nor ClassB are suitable.` – Atrotygma Jul 28 '13 at 22:03
1

Can someone explain to me how this works?

Sure - it's just an expression using the conditional operator to pick which value to use for the rest of the expression. The overall type of the expression is BaseClass because that's the exact type of the second operand, and the third operand can be converted to it. You need the cast on one of the operands, because the overall type of the expression has to be either the type of the second operand or the type of the third operand... but neither ClassA nor ClassB are suitable.

Also, is this considered as a good practice or are there any significant downsides I just can't see?

Well it looks pretty ugly to me. I'd use the null-coalescing operator instead, and extract it into a separate local variable for clarity:

BaseClass target = a ?? (BaseClass) b;
target.BaseProperty = "Jon Skeet prefers this approach";

You could still do it in one statement, but I find that less readable:

(a ?? (BaseClass) b).BaseProperty = "Jon Skeet doesn't like 'clever' code.";

Even better would be to declare either a or b as being of type BaseClass, at which point you don't need the cast.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Oooh, I wasn't aware of this operator - again, the magical box of the Great Wizard of all Sharpness has opened a bit more for me. But it seems that I can't use this approach as I'm in need of ClassB (not just it's base) later and downcasting is not possible in C#. :) The only way I could think of is creating a separate MapLaterToClassB local variable, but that's way more ugly IMHO. – Atrotygma Jul 28 '13 at 21:53
  • @Atrotygma: Um, downcasting most certainly *is* available in C#... but only if the object is actually the right type. So you could definitely declare `b` to be of type `BaseClass` and then cast it to `ClassB` later. Or stick with the cast to `BaseClass` in the conditional operator, of course. – Jon Skeet Jul 28 '13 at 21:54
  • How should that work? Where do the other variables of ClassB come from? I recently tried to use downcasting by creating a base class for enumerations similiar to the ones in Java (I don't like C# enums) by delivering the type of the derived class to the base class as generic type. Didn't work for explicit and implicit downcastings as you can see in here: http://stackoverflow.com/questions/9875545/casting-string-to-type-safe-enum-using-user-defined-conversion (Although the solution of the SO question didn't worked for me anyway.. strange) – Atrotygma Jul 28 '13 at 21:59
  • @Atrotygma: What do you mean, "where do they come from"? They're still in the object. The value of `b` is just a *reference* to the object. The problem in your other question was actually not the one identified in the accepted answer - I'll add another answer there in a minute. – Jon Skeet Jul 28 '13 at 22:04