Problem with if block using interfaces

We came up with a weird error that I've never seen before today. Here's a customer class and an interface to a strategy class (ICustomerStrategy) with two concrete implementations (GoodCustomer, BadCustomer):

   1:      public class Customer
   2:      {
   3:          private readonly int _creditLimit;
   4:   
   5:          public Customer(int creditLimit)
   6:          {
   7:              _creditLimit = creditLimit;
   8:          }
   9:   
  10:          public int CreditLimit
  11:          {
  12:              get { return _creditLimit; }
  13:          }
  14:      }
  15:   
  16:      public interface ICustomerStrategy
  17:      {
  18:      }
  19:   
  20:      internal class BadCustomer : ICustomerStrategy
  21:      {
  22:      }
  23:   
  24:      internal class GoodCustomer : ICustomerStrategy
  25:      {
  26:      }

In the consumer class we have a method called GetStrategy that returns an ICustomerStrategy object.

This statement using an if-block works fine:

   1:      public class Strategy
   2:      {
   3:          public ICustomerStrategy GetStrategy(Customer customer)
   4:          {
   5:              if (hasGoodCredit(customer))
   6:              {
   7:                  return new GoodCustomer();        
   8:              }
   9:   
  10:              return new BadCustomer();
  11:          }
  12:   
  13:          private static bool hasGoodCredit(Customer customer)
  14:          {
  15:              return customer.CreditLimit > 1000;
  16:          }
  17:      }

However this statement using the conditional operator doesn't:

   1:      public class Strategy
   2:      {
   3:          public ICustomerStrategy GetStrategy(Customer customer)
   4:          {
   5:              return hasGoodCredit(customer) ? new GoodCustomer() : new BadCustomer();
   6:          }
   7:   
   8:          private static bool hasGoodCredit(Customer customer)
   9:          {
  10:              return customer.CreditLimit > 1000;
  11:          }
  12:      }

The only way to make it work (thanks ReSharper for the tip!) is to cast the first object to the interface like so:

   1:          public ICustomerStrategy GetStrategy(Customer customer)
   2:          {
   3:              return hasGoodCredit(customer) ? (ICustomerStrategy) new GoodCustomer() : new BadCustomer();
   4:          }

You can also make it work by using a base abstract class rather than an interface, but I prefer the interface driven approach.

So the question on my noodle is... why?

4 Comments

  • Because the ternary operator can't figure out the type it should return.

    The rule is that the ternary will ALWAYS either return the type of the "true" expression OR the type of the "false" expression. It won't ever try to come up with a third type that's common to both, no matter how obvious that type may be.

    I find that pretty infuriating my own self (my pet example is "int? x = cond ? 1 : null;") but it's the way C# has always behaved, and I've had no luck trying to persuade the language designers to change it even for situations where it's "obvious" what the right type is.

    Your example is actually a case where it's not obvious at all; your GoodCustomer and BadCustomer might both have a common base class (other than Object) AND implement more than one interface in common. What type should the ternary return in that situation?

  • My guess without reading the Csharp spec on this is that the conditional operator needs to return a consistent type. When the left return value is a GoodCustomer, it tries to case the BadCustomer to a GoodCustomer. This doesn't work as thereis no explicit cast between the two classes.

    What would happen if the two classes shared another interface IDoStuff. Should the first example return an IDoStuff or an ICustomerStrategy?

  • Thanks for the insight guys. It makes sense to me, I've just never in 5 years of using this needed to return new objects so I can see where the issue is.

  • you're also not only casting the first object to the interface, but the second one as well.

    It works because im guessing that both implement the interface you're casting it too. If the second object didnt implement it, you probably would get a compiler error.

Comments have been disabled for this content.