如何对一个泛型类进行排序?

huangapple go评论85阅读模式
英文:

How to sort a generic class?

问题

如何对一个包含字符串和整数的类的列表进行排序。

编辑:我已经简化了查询代码。results2中的ToArray()方法生成了错误。为什么会这样?

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: '无法将类型为 'Cell1[System.String]' 的对象强制转换为类型 '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;

        // 根据值的类型进行比较
        if (Value is IComparable<T> comparableValue)
            return comparableValue.CompareTo(other.Value);

        // 如果值的类型不可比较,只比较文本值
        return Text.CompareTo(other.Text);
    }

    public override string ToString()
    {
        return Text;
    }
}
英文:

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 =&gt; ((Cell&lt;object&gt;)x.RowData[CurrentPlugin.SortOrderInfo.ColumnIndex]).Value);
var results2 = Entities.OrderBy(x =&gt; ((Cell&lt;object&gt;)x.RowData[CurrentPlugin.SortOrderInfo.ColumnIndex]).Value).ToArray();

> System.InvalidCastException: 'Unable to cast object of type 'Cell1[System.String]&#39; to type &#39;Cell1[System.Object]'.'

public class Cell&lt;T&gt; : IComparable&lt;Cell&lt;T&gt;&gt;
{
    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&lt;T&gt;? other)
    {
        if (other == null)
            return 1;

        // Compare based on the type of the values
        if (Value is IComparable&lt;T&gt; 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;
    }
}

答案1

得分: 4

以下是代码的翻译部分:

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

这段代码无法工作,因为Cell<T>不等于Cell<object>,除非T等于object(例如:https://stackoverflow.com/q/2033912/2501279)。即使你引入了协变接口ICell<out T>,对于int类型也无法工作,因为协变性只支持引用类型。

解决方法:

  1. 根据实际类型进行比较:

    如果你知道列表既可以包含整数又可以包含字符串,你可以这样做:

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

    如果你知道数据是同质的,你可以使用装箱的方法:

    x.OrderBy(o => o switch
     	{
     		Cell<int> ints => (object)ints.Value,
     		Cell<string> strings => strings.Value,
     		_ => null
     	})
     	.ToList();
    
  2. 实现非泛型的IComparable接口(不确定如何实现),然后进行类型转换。

  3. 对于同质的情况,你可以创建非泛型接口:

     interface ICell
     {
     	public object Value { get; }
     }
    

    并且以显式方式实现它,并进行类型转换。

  4. 实现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();
        }
    }
    

    并在OrderBy中使用它 - Entities.OrderBy(x => x, new StringOrIntCellComparer())

但可能你需要完全重新设计你的代码。

附注:

results2中使用ToArray()方法会生成错误

这是因为LINQ是延迟执行的,result1并没有实际执行过滤操作,而ToArray是一种实现操作符,会导致实际执行。

英文:

The following code:

.ThenBy(x =&gt; ((Cell&lt;object&gt;)x.RowData[CurrentPlugin.SortOrderInfo.ColumnIndex]).Value))

Will not work because Cell&lt;T&gt; is not Cell&lt;object&gt; unless T == object (for example - https://stackoverflow.com/q/2033912/2501279 ). Even if you will introduce covariant interface ICell&lt;out T&gt; 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 =&gt; o switch
     	{
     		Cell&lt;int&gt; ints =&gt; ints.Value.ToString(),
     		Cell&lt;string&gt; strings =&gt; strings.Value,
     		_ =&gt; null
     	})
     	.ToList();
    

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

    x.OrderBy(o =&gt; o switch
     	{
     		Cell&lt;int&gt; ints =&gt; (object)ints.Value,
     		Cell&lt;string&gt; strings =&gt; strings.Value,
     		_ =&gt; 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&lt;Entity&gt;:

    class StringOrIntCellComparer : IComparer&lt;Entity&gt;
    {
        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&lt;int&gt; xi &amp;&amp; y is Cell&lt;int&gt; yi)
            {
                return xi.Value.CompareTo(yi.Value); // or call compare from class
            }
    
            if (x is Cell&lt;string&gt; xs &amp;&amp; y is Cell&lt;string&gt; 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 =&gt; 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.

huangapple
  • 本文由 发表于 2023年7月27日 14:54:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76777159.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定