【问题标题】:Use own IComparer<T> with Linq OrderBy将自己的 IComparer<T> 与 Linq OrderBy 一起使用
【发布时间】:2009-06-12 08:49:48
【问题描述】:

我有一个通用的

List<MyClass>

其中MyClass 有一个属性InvoiceNumber,其中包含以下值:

200906/1
200906/2
..
200906/10
200906/11
200906/12

我的列表绑定到一个

BindingList<T>

支持linq排序:

protected override void ApplySortCore(
           PropertyDescriptor property, ListSortDirection direction)
{

    _sortProperty = property;
    _sortDirection = direction;

    var items = this.Items;

    switch (direction)
    {
        case ListSortDirection.Ascending:
            items = items.OrderByDescending(x => property.GetValue(x)).ToList();
            break;
        case ListSortDirection.Descending:
            items = items.OrderByDescending(x => property.GetValue(x)).ToList();
            break;
    }

    this.Items = items;

}

然而,默认的比较器排序(如假设的那样)如下:

200906/1
200906/10
200906/11
200906/12
200906/2

在这种情况下很糟糕。

现在我想用我自己的IComparer&lt;T&gt; 来处理这个。它看起来像这样:

public class MyComparer : IComparer<Object>
{

    public int Compare(Object stringA, Object stringB)
    {
        String[] valueA = stringA.ToString().Split('/');
        String[] valueB = stringB.ToString().Split('/');

        if(valueA .Length != 2 || valueB .Length != 2)
             return String.Compare(stringA.ToString(), stringB.ToString());

        if (valueA[0] == valueB[0]) 
        {
          return String.Compare(valueA[1], valueB[1]);
        }
        else
        {
          return String.Compare(valueA[0], valueB[0]);
        }

    }

}

并将ApplySortCore 代码更改为使用此IComparer:

case ListSortDirection.Ascending:
    MyComparer comparer = new MyComparer();
    items = items.OrderByDescending(
              x => property.GetValue(x), comparer).ToList();
    break;

当我调试我的代码时,我看到MyComparer.Compare(object, object) 被多次调用并为比较方法返回正确的值(-1、0、1)。

但我的列表仍然以“错误”的方式排序。我错过了什么吗?我不知道。

【问题讨论】:

    标签: c# linq sql-order-by icomparer


    【解决方案1】:

    您的比较器在我看来是错误的。您仍然只是按默认文本顺序排序。当然,您想解析这两个数字并以此为基础进行排序:

    public int Compare(Object stringA, Object stringB)
    {
        string[] valueA = stringA.ToString().Split('/');
        string[] valueB = stringB.ToString().Split('/');
    
        if (valueA.Length != 2 || valueB.Length != 2)
        {
            stringA.ToString().CompareTo(stringB.ToString());
        }
    
        // Note: do error checking and consider i18n issues too :)
        if (valueA[0] == valueB[0]) 
        {
            return int.Parse(valueA[1]).CompareTo(int.Parse(valueB[1]));
        }
        else
        {
            return int.Parse(valueA[0]).CompareTo(int.Parse(valueB[0]));
        }
    }
    

    (请注意,这与您的问题不相符,即您已经调试并验证了 Compare 正在返回正确的值 - 但我担心我怀疑这方面的人为错误。)

    此外,Sven 的权利 - 更改 items 的值根本不会更改您的绑定列表。您应该添加:

    this.Items = items;
    

    在方法的底部。

    【讨论】:

    • 对不起,我把代码缩短了一点。在我的原始代码中,我这样做了。Items = items; (如果不是,它无论如何都不会排序)但是 int 转换有效(我一定是盲目的 || 愚蠢地错过了那个)。非常感谢。
    【解决方案2】:

    我遇到了一般自然排序的问题,把解决方法写在这里:

    Natural Sort Compare with Linq OrderBy()

    public class NaturalSortComparer<T> : IComparer<string>, IDisposable
    {
        private bool isAscending;
    
        public NaturalSortComparer(bool inAscendingOrder = true)
        {
            this.isAscending = inAscendingOrder;
        }
    
        #region IComparer<string> Members
    
        public int Compare(string x, string y)
        {
            throw new NotImplementedException();
        }
    
        #endregion
    
        #region IComparer<string> Members
    
        int IComparer<string>.Compare(string x, string y)
        {
            if (x == y)
                return 0;
    
            string[] x1, y1;
    
            if (!table.TryGetValue(x, out x1))
            {
                x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
                table.Add(x, x1);
            }
    
            if (!table.TryGetValue(y, out y1))
            {
                y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
                table.Add(y, y1);
            }
    
            int returnVal;
    
            for (int i = 0; i < x1.Length && i < y1.Length; i++)
            {
                if (x1[i] != y1[i])
                {
                    returnVal = PartCompare(x1[i], y1[i]);
                    return isAscending ? returnVal : -returnVal;
                }
            }
    
            if (y1.Length > x1.Length)
            {
                returnVal = 1;
            }
            else if (x1.Length > y1.Length)
            { 
                returnVal = -1; 
            }
            else
            {
                returnVal = 0;
            }
    
            return isAscending ? returnVal : -returnVal;
        }
    
        private static int PartCompare(string left, string right)
        {
            int x, y;
            if (!int.TryParse(left, out x))
                return left.CompareTo(right);
    
            if (!int.TryParse(right, out y))
                return left.CompareTo(right);
    
            return x.CompareTo(y);
        }
    
        #endregion
    
        private Dictionary<string, string[]> table = new Dictionary<string, string[]>();
    
        public void Dispose()
        {
            table.Clear();
            table = null;
        }
    }
    

    【讨论】:

      【解决方案3】:

      我们不能这样做吗:

      public class MyComparer : IComparer<string>
      {
      
          public int Compare(string stringA, string stringB)
          {
              string small = stringA;
              string big = stringB;
              if (stringA.Length > stringB.Length)
              {
                  small = stringB;
                  big = stringA;
              }
              else if (stringA.Length < stringB.Length)
              {
                  small = stringA;
                  big = stringB;
              }
              for (int j = 0; j < small.Length; j++)
              {
                  if (Convert.ToInt32(small[j]) > Convert.ToInt32(big[j])) return -1;
                  if (Convert.ToInt32(small[j]) < Convert.ToInt32(big[j])) return 1;
              }
      
              //big is indeed bigger
              if (big.Length > small.Length) return 1;
      
              //finally they are smae
              return 0;
          }
      }
      

      用法:

      string[] inputStrings = {"_abc*&","#almnp","abc" };
      //string[] inputStrings = { "#", "_", "_a", "@", "_" };
      MyComparer computer = new MyComparer();
      var kola = inputStrings.OrderBy(x => x, new MyComparer()).ToArray();
      

      这与:

          Array.Sort(inputStrings, StringComparer.Ordinal);
      

      【讨论】:

        【解决方案4】:

        您可以使用 Alphanum 算法:

          (...)
            items.OrderBy(x => property.GetValue(x), new AlphanumComparator())
          (...)
        

        AlphanumComparator

        /*
         * The Alphanum Algorithm is an improved sorting algorithm for strings
         * containing numbers.  Instead of sorting numbers in ASCII order like
         * a standard sort, this algorithm sorts numbers in numeric order.
         *
         * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
         *
         * Based on the Java implementation of Dave Koelle's Alphanum algorithm.
         * Contributed by Jonathan Ruckwood <jonathan.ruckwood@gmail.com>
         *
         * Adapted by Dominik Hurnaus <dominik.hurnaus@gmail.com> to
         *   - correctly sort words where one word starts with another word
         *   - have slightly better performance
         *
         * Released under the MIT License - https://opensource.org/licenses/MIT
         *
         * Permission is hereby granted, free of charge, to any person obtaining
         * a copy of this software and associated documentation files (the "Software"),
         * to deal in the Software without restriction, including without limitation
         * the rights to use, copy, modify, merge, publish, distribute, sublicense,
         * and/or sell copies of the Software, and to permit persons to whom the
         * Software is furnished to do so, subject to the following conditions:
         *
         * The above copyright notice and this permission notice shall be included
         * in all copies or substantial portions of the Software.
         *
         * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
         * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
         * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
         * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
         * USE OR OTHER DEALINGS IN THE SOFTWARE.
         *
         */
        using System;
        using System.Collections;
        using System.Text;
        
        /*
         * Please compare against the latest Java version at http://www.DaveKoelle.com
         * to see the most recent modifications
         */
        namespace AlphanumComparator
        {
            public class AlphanumComparator : IComparer
            {
                private enum ChunkType {Alphanumeric, Numeric};
                private bool InChunk(char ch, char otherCh)
                {
                    ChunkType type = ChunkType.Alphanumeric;
        
                    if (char.IsDigit(otherCh))
                    {
                        type = ChunkType.Numeric;
                    }
        
                    if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
                        || (type == ChunkType.Numeric && !char.IsDigit(ch)))
                    {
                        return false;
                    }
        
                    return true;
                }
        
                public int Compare(object x, object y)
                {
                    String s1 = x as string;
                    String s2 = y as string;
                    if (s1 == null || s2 == null)
                    {
                        return 0;
                    }
        
                    int thisMarker = 0, thisNumericChunk = 0;
                    int thatMarker = 0, thatNumericChunk = 0;
        
                    while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
                    {
                        if (thisMarker >= s1.Length)
                        {
                            return -1;
                        }
                        else if (thatMarker >= s2.Length)
                        {
                            return 1;
                        }
                        char thisCh = s1[thisMarker];
                        char thatCh = s2[thatMarker];
        
                        StringBuilder thisChunk = new StringBuilder();
                        StringBuilder thatChunk = new StringBuilder();
        
                        while ((thisMarker < s1.Length) && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0])))
                        {
                            thisChunk.Append(thisCh);
                            thisMarker++;
        
                            if (thisMarker < s1.Length)
                            {
                                thisCh = s1[thisMarker];
                            }
                        }
        
                        while ((thatMarker < s2.Length) && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0])))
                        {
                            thatChunk.Append(thatCh);
                            thatMarker++;
        
                            if (thatMarker < s2.Length)
                            {
                                thatCh = s2[thatMarker];
                            }
                        }
        
                        int result = 0;
                        // If both chunks contain numeric characters, sort them numerically
                        if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
                        {
                            thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
                            thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
        
                            if (thisNumericChunk < thatNumericChunk)
                            {
                                result = -1;
                            }
        
                            if (thisNumericChunk > thatNumericChunk)
                            {
                                result = 1;
                            }
                        }
                        else
                        {
                            result = thisChunk.ToString().CompareTo(thatChunk.ToString());
                        }
        
                        if (result != 0)
                        {
                            return result;
                        }
                    }
        
                    return 0;
                }
            }
        }
        

        【讨论】:

          【解决方案5】:

          已排序列表仅绑定到局部变量 items,而不绑定到绑定列表的 Items 属性,因此它保持未排序。

          [编辑] 基本上,您只是在丢弃排序工作的结果;-)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-07-25
            • 1970-01-01
            • 2010-10-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多