2

How can I sort a list of this class where T in the list can be string and integers.

Edit: I have simplified the query code. The ToArray() method in results2 generates the error. Why is that?

var results1 = Entities.OrderBy(x => ((Cell<object>)x.RowData[CurrentPlugin.SortOrderInfo.ColumnIndex]).Value);
var results2 = Entities.OrderBy(x => ((Cell<object>)x.RowData[CurrentPlugin.SortOrderInfo.ColumnIndex]).Value).ToArray();

System.InvalidCastException: 'Unable to cast object of type 'Cell1[System.String]' to type 'Cell1[System.Object]'.'

public class Cell<T> : IComparable<Cell<T>>
{
    public Cell()
    {
        Text = string.Empty;
        Value = default;
    }
    public Cell(int index)
    {
        Index = index;
        Text = string.Empty;
        Value = default;
    }
    public Cell(string text)
    {
        Text = text;
        Value = default;
    }
    public Cell(string text, T value)
    {
        Text = text;
        Value = value;
    }

    public int Index { get; set; }
    public string Text { get; set; }
    public T Value { get; set; }

    public int CompareTo(Cell<T>? other)
    {
        if (other == null)
            return 1;

        // Compare based on the type of the values
        if (Value is IComparable<T> comparableValue)
            return comparableValue.CompareTo(other.Value);

        // If the Value type is not comparable, just compare the Text values
        return Text.CompareTo(other.Text);
    }

    public override string ToString()
    {
        return Text;
    }
}
Amir Rezaei
  • 4,948
  • 6
  • 33
  • 49
  • 2
    What is the problem with your code? – Sweeper Jul 27 '23 at 06:55
  • How can I sort a list of this class where T in the list can be string and integers. – Amir Rezaei Jul 27 '23 at 06:58
  • `List> list = ...`, `list.Sort()`. Or `var sorted = list.Order().ToList()` (`list.OrderBy(x => x).ToList()` for older frameworks) – Guru Stron Jul 27 '23 at 07:00
  • The list contains Cell and Cell. – Amir Rezaei Jul 27 '23 at 07:01
  • @AmirRezaei you are answering to me? `List> list` is a list containing `Cell`. – Guru Stron Jul 27 '23 at 07:02
  • If you are talking about `List` (or `List>`)- then try avoid doing that. Also there is no "natural" comparison between string and integer, you will need to write custom comparer implementing such logic and pass it to `Sort` or `Order`. – Guru Stron Jul 27 '23 at 07:05
  • In short - provide a [mre] with some sample inputs and desired outputs. Currently it is not clear what you are trying to do and what the actual problem is. – Guru Stron Jul 27 '23 at 07:07
  • What is difference between `new Cell(string.Empty)` and `new Cell(string.Empty, 0)` ? In my opinion the class is bad designed. You should be more based on `T` parameter. – fdafadf Jul 27 '23 at 07:13

1 Answers1

4

The following code:

.ThenBy(x => ((Cell<object>)x.RowData[CurrentPlugin.SortOrderInfo.ColumnIndex]).Value))

Will not work because Cell<T> is not Cell<object> unless T == object (for example - C# variance problem: Assigning List<Derived> as List<Base> ). Even if you will introduce covariant interface ICell<out T> it still will not work for int's because variance is supported only for reference types.

Workarounds:

  1. Compare against actual type:

    If you know that list can contain both ints and strings then you can do something like:

    x.OrderBy(o => o switch
         {
             Cell<int> ints => ints.Value.ToString(),
             Cell<string> strings => strings.Value,
             _ => null
         })
         .ToList();
    

    If you know that data is homogeneous then you can go with boxing approach:

    x.OrderBy(o => o switch
         {
             Cell<int> ints => (object)ints.Value,
             Cell<string> strings => strings.Value,
             _ => null
         })
         .ToList();
    
  2. Implement non-generic IComparable (not sure how though) and cast to it.

  3. In case of homogeneous you can create non-generic interface:

     interface ICell
     {
         public object Value { get; }
     }
    

    and implement it explicitly and cast to it.

  4. Implement IComparer<Entity>:

    class StringOrIntCellComparer : IComparer<Entity>
    {
        public int Compare(Entity? left, Entity? right)
        {
            var x = left.RowData[CurrentPlugin.SortOrderInfo.ColumnIndex];
            var y = right.RowData[CurrentPlugin.SortOrderInfo.ColumnIndex];
            if (x is Cell<int> xi && y is Cell<int> yi)
            {
                return xi.Value.CompareTo(yi.Value); // or call compare from class
            }
    
            if (x is Cell<string> xs && y is Cell<string> ys)
            {
                return xs.Value.CompareTo(ys.Value); // or call compare from class
            }
    
            // todo: type mismatch or unsupported type
            throw new Exception();
        }
    }
    

    and use in in the OrderBy - Entities.OrderBy(x => x, new StringOrIntCellComparer())

But possibly you need to rework your design completely.

P.S.

The ToArray() method in results2 generates the error

Because LINQ is lazy and result1 does not actually perform filtering while ToArray is one of the materialization operators which results in actual execution.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132