C#Array.Sort源代码解析

public static void Sort(Array array)
{
	if (array == null)
	{
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
	}
	Sort(array, null, array.GetLowerBound(0), array.Length, null);
}

第一个if判断语句为,判断传入的数组是否为空,从而根据数组异常的类型决定是否抛出异常,这里我们不做深入探讨(我也不会),我们主要看下面的Sort()方法。

public static void Sort(Array keys, Array? items, int index, int length, IComparer? comparer)

该方法传入5个参数

1.Array key:就是你传入的数组 我们可以叫它键数组 keys,为什么怎么叫请继续往下看。

2.Array?itrams:一个可空的项数组 items(Array?为可空类型,想了解相关知识可自行搜索)。

注意上面的Sort()方法Array?items 参数位置直接写为null,是以为我们使用的是Sort(Array array)的重载,不包含Array?iteams 参数,所以直接写为null。

下面我们使用Sort(Array array,Array?iteam)重载为你解释Array?iteam的作用

主要作用:与你传入的数组一起进行排序

什么意思呢,我们举例来说明

  
            string[] strings = { "c", "a", "e", "b", "d" };
            int[] ints = { 4, 1, 5, 2, 3 };
            Array.Sort(strings, ints);
        foreach (string s in strings)
            {
                Console.Write(s+" ");
            }
            Console.WriteLine();
            foreach (int i in ints)
            {
                Console.Write(i+" ");
            }

我们传入两个数组,strings里的字母与ints里的数字相对应例如“a”->1;

然后我们使用   Array.Sort(strings, ints);对strings数组进行排序,那么Sort方法会根据strings排序好的顺序,在对ints进行排序。

这也是我们为啥叫.Array key为键数组的原因,因为key[i]与iteam[i]互相对应

3. int index:表示你想要开始排序的位置

而我们Sort(Array array)这个重载中使用了array.GetLowerBound(0)来代替了index。

我们首先来解释一下array.GetLowerBound(0):

array.GetLowerBound(0) 方法用于获取指定数组的第一个维度的下界(索引的开始位置)(lower bound)。

维度:表示你的数组是一维,还是二维数组。

我们都知道多维数组的索引都是从0开始的所以array.GetLowerBound(0)的值始终为0,你无法去改变array.GetLowerBound(0)的值,所以当我们不需要指定需要排序数组的开始位置时使用array.GetLowerBound(0)来代替0,至于为什么,我也不知道是因为安全问题吗?

4. int length:,一个排序的长度 length及数组的长度,在此重载中无需指定长度所以使用array.Length来代替。

5.IComparer? comparer 一个可空的比较器 comparer 作为参数。

那么这个参数到底有什么意义呢?作用又是什么呢?

5.1从IComparer命名来看,这应该是一个接口类型。

我们都知道Array.Sort()方法可以对C#内部的基类进行排序(int,float,string等),但是它无法对我们自己定义的类型进行排序

例如我们创建一个Player类并且定义两个属性。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_进阶
{
    internal class Player
    {
        public int hp;
        public string name;
        public int Hp
        {
            get { return hp; }
            set
            {
                if (value < 0 || value > 100)
                {
                    Console.WriteLine("Hp赋值错误");
                }
                else
                {
                    hp = value;
                }
            }
        }
        public string Name
        {
            get { return name; }
            set
            {
                if (value!= null)
                {
                    name= value;
                }
                else
                {
                    Console.WriteLine("Name为null");
                }
            }
        }
    }
}

然后我们创建一个Player数组,我希望可以通过Array.Sort()方法对Player数组中的Hp进行排序

  Player[] players =
  {
      new Player() { Hp = 40, Name = "a" },
      new Player() { Hp = 10, Name = "b" },
      new Player() { Hp = 70, Name = "c" },
      new Player() { Hp = 5, Name = "d" }
  };
  Array.Sort(players);

显然这样做是不可能的,控制台会报这样一个错误。


内部异常 1:
ArgumentException: At least one object must implement IComparable.

您遇到的问题是“ArgumentException: 至少一个对象必须实现 IComparable 接口

所以我们对Player类实现这个接口就可以对Player数组进行排序

该接口要求我们实现一个CompareTo(object?obj)方法,obj就代表我们传入的想要比较的类型,

而Hp.CompareTo()方法是int类型的自带方法public Int32 CompareTo(Int32 value);

public int CompareTo(int value)
{
	if (this < value)
	{
		return -1;
	}
	if (this > value)
	{
		return 1;
	}
	return 0;
}

这段代码是一个用于比较两个整数的方法。它使用了 CompareTo 方法来将当前整数对象与另一个整数进行比较。

具体代码逻辑如下:

  1. 首先通过比较当前整数对象 this 和另一个整数 value 的大小关系来进行比较。
  2. 如果当前整数对象小于 value,则返回 -1,表示当前整数对象在排序上位于 value 之前。
  3. 如果当前整数对象大于 value,则返回 1,表示当前整数对象在排序上位于 value 之后。
  4. 如果当前整数对象等于 value,则返回 0,表示两个整数相等。

这种实现方式是基于整数类型的 CompareTo 方法的常规实现方式,用于在排序或比较操作中确定对象之间的相对顺序。

那么为什么是返回0,1,-1呢,按照常理来说不应该是返回那个比较大或小的数吗?

  好问题!实际上,返回 1、0 和 -1 是一种约定俗成的规定,用于在比较操作中表示对象之间的相对顺序。返回 1 表示当前对象在排序中应该位于比较对象之后,而返回 -1 则表示当前对象应该位于比较对象之前。而返回 0 表示两个对象相等,它们在排序中应该被视为相同的位置。这种约定可以使比较方法统一地返回某种整数值,使得它们可以方便地用于排序算法和其他比较操作。虽然可以选择返回比较大或比较小的那个数作为结果,但这种约定更符合通用实践和约定,并且在标准的排序算法和集合类中广泛应用。所以,当我们使用 CompareTo 方法进行对象比较时,可以依赖于返回值 1、0 和 -1 来判断相对顺序和相等性。

至于返回的1、0、-1,去了哪里这个我就不知道了,可能是去了另外一个方法体能然后根据1、0、-1,来决定是否交换两个值。

这样我们就可以对我们自己定义的类型进行排序了。

但我们好像偏移了我们的主题ICompare接口,不要着急我们继续往下看。

通过面向对象的思想我们应该知道高内聚低耦合的概念,所以我们不应该对Player这个类去进行修改,我们应该新建一个Player Compare类去完成这个比较,所以微软为我们提供了ICompare接口。

 internal class PlayerCompare : IComparer<Player>
 {
    
     int IComparer<Player>.Compare(Player? x, Player? y)
     {
         if (x == null && y == null)
         {
             return 0; // 两个对象都为空,认为它们相等
         }
         else if (x == null)
         {
             return -1; // x 为空,y 不为空,认为 x 小于 y
         }
         else if (y == null)
         {
             return 1; // y 为空,x 不为空,认为 x 大于 y
         }
         else
         {
             return x.Hp.CompareTo(y.Hp); // 比较两个对象的年龄属性
         }
     }

 }

这里我们使用ICompare<>泛型方法,这样就省去了装箱的过程让代码看起来更加简洁。

要使用ICompare需要使用Array.Sort(Array array,ICompare? compare)重载,Array.Sort(Array array)方法没有使用到这个可空的比较器,所以直接赋值为了null。 

下面为大家展示一下源码

public static void Sort(Array array)
{
	if (array == null)
	{
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
	}
	Sort(array, null, array.GetLowerBound(0), array.Length, null);
}
public static void Sort(Array keys, Array? items, int index, int length, IComparer? comparer)
{
	if (keys == null)
	{
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keys);
	}
	if (keys.Rank != 1 || (items != null && items.Rank != 1))
	{
		ThrowHelper.ThrowRankException(ExceptionResource.Rank_MultiDimNotSupported);
	}
	int lowerBound = keys.GetLowerBound(0);
	if (items != null && lowerBound != items.GetLowerBound(0))
	{
		ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_LowerBoundsMustMatch);
	}
	if (index < lowerBound)
	{
		ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
	}
	if (length < 0)
	{
		ThrowHelper.ThrowLengthArgumentOutOfRange_ArgumentOutOfRange_NeedNonNegNum();
	}
	if (keys.Length - (index - lowerBound) < length || (items != null && index - lowerBound > items.Length - length))
	{
		ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
	}
	if (length <= 1)
	{
		return;
	}
	if (comparer == null)
	{
		comparer = Comparer.Default;
	}
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
	if (keys is object[] keys2)
	{
		object[] array = items as object[];
		if (items == null || array != null)
		{
			new SorterObjectArray(keys2, array, comparer).Sort(index, length);
			return;
		}
	}
	if (comparer == Comparer.Default)
	{
		CorElementType corElementTypeOfElementType = keys.GetCorElementTypeOfElementType();
		if (items == null || items.GetCorElementTypeOfElementType() == corElementTypeOfElementType)
		{
			int adjustedIndex2 = index - lowerBound;
			switch (corElementTypeOfElementType)
			{
			case CorElementType.ELEMENT_TYPE_I1:
				GenericSort<sbyte>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_BOOLEAN:
			case CorElementType.ELEMENT_TYPE_U1:
				GenericSort<byte>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_I2:
				GenericSort<short>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_CHAR:
			case CorElementType.ELEMENT_TYPE_U2:
				GenericSort<ushort>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_I4:
				GenericSort<int>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_U4:
				GenericSort<uint>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_I8:
			case CorElementType.ELEMENT_TYPE_I:
				GenericSort<long>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_U8:
			case CorElementType.ELEMENT_TYPE_U:
				GenericSort<ulong>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_R4:
				GenericSort<float>(keys, items, adjustedIndex2, length);
				return;
			case CorElementType.ELEMENT_TYPE_R8:
				GenericSort<double>(keys, items, adjustedIndex2, length);
				return;
			}
		}
	}
	new SorterGenericArray(keys, items, comparer).Sort(index, length);
	static void GenericSort<T>(Array keys, Array items, int adjustedIndex, int length) where T : struct
	{
		Span<T> span = UnsafeArrayAsSpan<T>(keys, adjustedIndex, length);
		if (items != null)
		{
			span.Sort(UnsafeArrayAsSpan<T>(items, adjustedIndex, length));
		}
		else
		{
			span.Sort();
		}
	}
}

 横线上面的代码基本都是做一些安全判断,进行了一些参数校验,确保 keys 数组和 items 数组是一维数组,并且它们的长度和索引范围是合法的。

然后,代码根据给定的比较器(comparer)对数组进行排序。如果没有指定比较器,则使用默认的比较器(Comparer.Default)。

如果 keys 数组是一个对象数组(object[]),并且 items 数组也是一个对象数组或者为空,代码使用 SorterObjectArray 类对它们进行排序。

否则,代码根据 keys 数组元素的类型来选择合适的排序方法。根据元素类型的不同,调用对应的 GenericSort 方法,使用泛型和 Span 对数组进行排序。

最后,代码返回排序后的数组。

本人实力有限只能为大家讲解一下自己的一些理解和Ai的一些补充,如有错误请大家帮忙指出。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

love.南亭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值