IComparer child as a generic param could not be

2019-09-07 17:46发布

问题:

Got stuck trying to sort my List<> in C#.

I have an interface type with its implementation class:

public interface IRoom
{
    // ...
}

public class Room : IRoom
{
    // ...
}

And a base class for my comparers:

public abstract class RoomComparer : IComparer<IRoom>
{
    public RoomComparer()
    {
    }

    public abstract int Compare(IRoom x, IRoom y);
}

And two its children:

public class StandardMultipliedSquareMetersComparer : RoomComparer
{
    public StandardMultipliedSquareMetersComparer()
    {
    }

    public override int Compare(IRoom a, IRoom b)
    {
        // ...
    }
}

public class SquareMetersComparer : RoomComparer
{
    public SquareMetersComparer()
    {
    }

    public override int Compare(IRoom a, IRoom b)
    {
        // ...
    }
}

Now, here's where the problems begin: I got a generic class Hotel with a list of my Room instances:

public class Hotel<TRoom, TComparer> : IHotel, IEnumerable<TRoom> 
    where TRoom : IRoom 
    where TComparer : RoomComparer, new()
{
    public List<TRoom> Rooms;
    protected TComparer Comparer;

    public Hotel(TRoom[] rooms)
    {
        Rooms = new List<TRoom>();
        Rooms.AddRange(rooms);
        Comparer = new TComparer();
        Rooms.Sort(Comparer);
    }
}

And here's the trouble - I got two errors on the line Rooms.Sort(Comparer);:

Error CS1502: The best overloaded method match for `System.Collections.Generic.List.Sort(System.Collections.Generic.IComparer)' has some invalid arguments (CS1502)

Error CS1503: Argument #1' cannot convertTComparer' expression to type `System.Collections.Generic.IComparer' (CS1503)

I tried many different solutions, but no result. What's happening?

Note: using Mono 3.10.0 on Ubuntu

回答1:

You're trying to use generic contravariance for this (if a comparer can compare two TRoom values, it should be able to compare two IRoom values), which would usually be fine - but only when TRoom is known to be a class type. You can fix that by requiring that TRoom is a reference type:

where TRoom : class, IRoom

At that point, contravariance works fine, and all is well. It does require that all your room types are genuinely classes, of course.

Do you definitely need the hotel to be generic like this? It seems a little too much like overkill to me...



回答2:

Here's a way to fix this:

where TComparer : IComparer<IRoom>, new()

(as for the why, see Jon's answer)

You could also simplify your code somewhat:

Rooms = rooms.OrderBy(room => room, new TComparer()).ToList();

(just add using System.Linq)

See working demo