1

I'm using a factory to create IComparer<User> objects to sort a list of users.

I have 2 classes : Ascending and Descending, both implement IComparer<User>. Here's the code :

namespace Test
{
    public class Program
    {
        public static void Main(string[] args)
        {
            List<User> users = new List<User>();
            users.Add(new User("foo", "bar"));
            // ...
            IComparer<User> cmp = ComparerFactory.GetComparer("FirstName", true);
            if (cmp != null)
            {
                users.Sort(cmp);
            }
        }
    }

    public class User
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public User(string firstName, string lastName)
        {
            FirstName = firstName;
            LastName = lastName;
        }
    }

    public class UserFirstNameComparer
    {
        public class Ascending : IComparer<User>
        {
            public int Compare(User u1, User u2)
            {
                return String.Compare(u1.FirstName, u2.FirstName, true);
            }
        }

        public class Descending : IComparer<User>
        {
            public int Compare(User u1, User u2)
            {
                return new UserFirstNameComparer.Ascending().Compare(u1, u2) * -1;
            }
        }
    }

    public static class ComparerFactory
    {
        public static IComparer<User> GetComparer(string fieldName, bool ascending)
        {
            switch (fieldName)
            {
                case "FirstName":
                    return ascending ?
                        new UserFirstNameComparer.Ascending() : // ERROR IS HERE
                        new UserFirstNameComparer.Descending();
                 //...
            }
            return null;
        }
    }

But I get an error (line : new UserFirstNameComparer.Ascending() :) :

Type of conditional expression cannot be determined because there is no implicit conversion between 'Test.UserFirstNameComparer.Ascending' and 'Test.UserFirstNameComparer.Descending'

I dont understand what it means, both are IComparer objects, so what's the problem ? The weird thing is that I can fix the error with a (unnecessary ?) cast :

// This works
return ascending ?
    (IComparer<User>) new UserFirstNameComparer.Ascending() :
    new UserFirstNameComparer.Descending();


// This works too
return ascending ?
    new UserFirstNameComparer.Ascending() :
    (IComparer<User>) new UserFirstNameComparer.Descending();

Of course it works when I cast in both cases. But I do not understand why it works with only one cast, and why it does not when there is no cast. Any ideas ?

(I'm using VS 2012, .NET v4.0.30319)

leppie
  • 115,091
  • 17
  • 196
  • 297
Junior Dussouillez
  • 2,327
  • 3
  • 30
  • 39
  • 1
    See the answer to this question: http://stackoverflow.com/questions/202271/why-is-this-code-invalid-in-c – Dennis_E Jul 09 '14 at 11:58
  • Instead of having two `Comparers` (which is very silly), why don't you just create one regular `Comparer` (ascending) and do list.Sort(cmp).Reverse() or list.OrderBy(cmp) / list.OrderByDescending(cmp)? – Davio Jul 09 '14 at 12:01
  • 1
    Note that multiplying by `-1` as you do above, will not work as desired if the return value of the `Compare` call is `-2147483648`. Some `Compare` implementations do return other values than just `-1`, `0` and `+1`, and are allowed to do so. – Jeppe Stig Nielsen Jul 09 '14 at 12:17

2 Answers2

4

new UserFirstNameComparer.Ascending() returns an instance of Ascending. new UserFirstNameComparer.Descending() returns an instance of Descending.

Even though both implement IComparer, the type of these instances is different. When you cast one of them to IComparer, the resulting object has type IComparer. Now it is possible to do an implicit conversion, since one is an interface and the other a class implementing it.

To quote the documentation, for the conditional expression

condition ?: first_expression : second_expression

Either the type of first_expression and second_expression must be the same, or an implicit conversion must exist from one type to the other.

shree.pat18
  • 21,449
  • 3
  • 43
  • 63
4

The expression

cond ? X : Y

just requires (simplified) that either the compile-time type of X is implicitly convertible to the compile-time type of Y, or vice versa. It is not going to search through all interfaces and base classes of the two types to try and find some "common" type of them. (Even if it did, how would it handle the case with multiple common interfaces?)

This is how the language is designed. It is not relevant what the return type of your method is, that cannot be taken into consideration when trying to resolve a ?: expression.

You found the solution already, to cast X and/or Y to the desired type, explicitly.


Having answered your question, I also have a suggestion on how you can do this without writing your two classes Ascending and Descending. You can create an IComparer<> like this:

return ascending
  ? Comparer<User>.Create((u1, u2) => String.Compare(u1.FirstName, u2.FirstName, true))
  : Comparer<User>.Create((u1, u2) => String.Compare(u2.FirstName, u1.FirstName, true))
  ;

so no need (possibly) for those two classes of yours.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 1
    I should mention that the [`Comparer<>.Create` method](http://msdn.microsoft.com/en-us/library/hh737198.aspx) was new in .NET 4.5 (2012). Delegates of type `Comparison<>` have existed since .NET 2.0 (2005). – Jeppe Stig Nielsen Jul 09 '14 at 12:35