C#高级编程(第10版) C# 6 & .NET Core 1.0 (.NET开发经典名著)
上QQ阅读APP看书,第一时间看更新

7.5 Array类

用方括号声明数组是C#中使用Array类的表示法。在后台使用C#语法,会创建一个派生自抽象基类Array的新类。这样,就可以使用Array类为每个C#数组定义的方法和属性了。例如,前面就使用了Length属性,或者使用foreach语句迭代数组。其实这是使用了Array类中的GetEnumerator()方法。

Array类实现的其他属性有LongLength和Rank。如果数组包含的元素个数超出了整数的取值范围,就可以使用LongLength属性来获得元素个数。使用Rank属性可以获得数组的维数。

下面通过了解不同的功能来看看Array类的其他成员。

7.5.1 创建数组

Array类是一个抽象类,所以不能使用构造函数来创建数组。但除了可以使用C#语法创建数组实例之外,还可以使用静态方法CreateInstance()创建数组。如果事先不知道元素的类型,该静态方法就非常有用,因为类型可以作为Type对象传递给CreateInstance()方法。

下面的例子说明了如何创建类型为int、大小为5的数组。CreateInstance()方法的第1个参数应是元素的类型,第2个参数定义数组的大小。可以用SetValue()方法设置对应元素的值,用GetValue()方法读取对应元素的值(代码文件SimpleArrays/Program.cs):

        Array intArray1 = Array.CreateInstance(typeof(int), 5);
        for (int i = 0; i < 5; i++)
        {
          intArray1.SetValue(33, i);
        }
        for (int i = 0; i < 5; i++)
        {
          WriteLine(intArray1.GetValue(i));
        }

还可以将已创建的数组强制转换成声明为int[]的数组:

        int[] intArray2 = (int[])intArray1;

CreateInstance()方法有许多重载版本,可以创建多维数组和不基于0的数组。下面的例子就创建了一个包含2×3个元素的二维数组。第一维基于1,第二维基于10:

        int[] lengths = { 2, 3 };
        int[] lowerBounds = { 1, 10 };
        Array racers = Array.CreateInstance(typeof(Person), lengths, lowerBounds);

SetValue()方法设置数组的元素,其参数是每一维的索引:

        racers.SetValue(new Person
        {
          FirstName = "Alain",
          LastName = "Prost"
        }, 1, 10);
        racers.SetValue(new Person
        {
          FirstName = "Emerson",
          LastName = "Fittipaldi"
        }, 1, 11);
        racers.SetValue(new Person
        {
          FirstName = "Ayrton",
          LastName = "Senna"
        }, 1, 12);
        racers.SetValue(new Person
        {
          FirstName = "Michael",
          LastName = "Schumacher"
        }, 2, 10);
        racers.SetValue(new Person
        {
          FirstName = "Fernando",
          LastName = "Alonso"
        }, 2, 11);
        racers.SetValue(new Person
        {
          FirstName = "Jenson",
          LastName = "Button"
        }, 2, 12);

尽管数组不是基于0,但可以用一般的C#表示法为它赋予一个变量。只需要注意不要超出边界即可:

        Person[, ] racers2 = (Person[, ])racers;
        Person first = racers2[1, 10];
        Person last = racers2[2, 12];

7.5.2 复制数组

因为数组是引用类型,所以将一个数组变量赋予另一个数组变量,就会得到两个引用同一数组的变量。而复制数组,会使数组实现ICloneable接口。这个接口定义的Clone()方法会创建数组的浅表副本。

如果数组的元素是值类型,以下代码段就会复制所有值,如图7-5所示:

图7-5

        int[] intArray1 = {1, 2};
        int[] intArray2 = (int[])intArray1.Clone();

如果数组包含引用类型,则不复制元素,而只复制引用。图7-6显示了变量beatles和beatlesClone,其中beatlesClone通过从beatles中调用Clone()方法来创建。beatles和beatlesClone引用的Person对象是相同的。如果修改beatlesClone中一个元素的属性,就会改变beatles中的对应对象(代码文件SimpleArray/Program.cs)。

        Person[] beatles = {
                            new Person { FirstName="John", LastName="Lennon" },
                            new Person { FirstName="Paul", LastName="McCartney" }
                          };
        Person[] beatlesClone = (Person[])beatles.Clone();

图7-6

除了使用Clone()方法之外,还可以使用Array.Copy()方法创建浅表副本。但Clone()方法和Copy()方法有一个重要区别:Clone()方法会创建一个新数组,而Copy()方法必须传递阶数相同且有足够元素的已有数组。

注意:如果需要包含引用类型的数组的深层副本,就必须迭代数组并创建新对象。

7.5.3 排序

Array类使用Quicksort算法对数组中的元素进行排序。Sort()方法需要数组中的元素实现IComparable接口。因为简单类型(如System.String和System.Int32)实现IComparable接口,所以可以对包含这些类型的元素排序。

在示例程序中,数组名称包含string类型的元素,这个数组可以排序(代码文件SortingSample/Program.cs)。

        string[] names = {
              "Christina Aguilera",
              "Shakira",
              "Beyonce",
              "Lady Gaga"
            };
        Array.Sort(names);
        foreach (var name in names)
        {
          WriteLine(name);
        }

该应用程序的输出是排好序的数组:

        Beyonce
        Christina Aguilera
        Lady Gaga
        Shakira

如果对数组使用自定义类,就必须实现IComparable接口。这个接口只定义了一个方法CompareTo(),如果要比较的对象相等,该方法就返回0。如果该实例应排在参数对象的前面,该方法就返回小于0的值。如果该实例应排在参数对象的后面,该方法就返回大于0的值。

修改Person类,使之实现IComparable<Person>接口。先使用String类中的CompareTo()方法对LastName的值进行比较。如果LastName的值相同,就比较FirstName(代码文件SortingSample/Person.cs):

        public class Person: IComparable<Person>
        {
          public int CompareTo(Person other)
          {
            if (other == null) return 1;
            int result = string.Compare(this.LastName, other.LastName);
            if (result == 0)
            {
              result = string.Compare(this.FirstName, other.FirstName);
            }
            return result;
          }
          //...

现在可以按照姓氏对Person对象对应的数组排序(代码文件SortingSample/Program.cs):

        Person[] persons = {
                new Person { FirstName="Damon", LastName="Hill" },
                new Person { FirstName="Niki", LastName="Lauda" },
                new Person { FirstName="Ayrton", LastName="Senna" },
                new Person { FirstName="Graham", LastName="Hill" }
            };
            Array.Sort(persons);
            foreach (var p in persons)
            {
              WriteLine(p);
            }

使用Person类的排序功能,会得到按姓氏排序的姓名:

        Damon Hill
        Graham Hill
        Niki Lauda
        Ayrton Senna

如果Person对象的排序方式与上述不同,或者不能修改在数组中用作元素的类,就可以实现IComparer接口或IComparer<T>接口。这两个接口定义了方法Compare()。要比较的类必须实现这两个接口之一。IComparer接口独立于要比较的类。这就是Compare()方法定义了两个要比较的参数的原因。其返回值与IComparable接口的CompareTo()方法类似。

类PersonComparer实现了IComparer<Person>接口,可以按照firstName或lastName对Person对象排序。枚举PersonCompareType定义了可用于PersonComparer的排序选项:FirstName和LastName。排序方式由PersonComparer类的构造函数定义,在该构造函数中设置了一个PersonCompareType值。实现Compare()方法时用一个switch语句指定是按FirstName还是LastName排序(代码文件SortingSample/PersonComparer.cs)。

        public enum PersonCompareType
        {
          FirstName,
          LastName
        }
        public class PersonComparer: IComparer<Person>
        {
          private PersonCompareType _compareType;
          public PersonComparer(PersonCompareType compareType)
          {
            _compareType = compareType;
          }
          public int Compare(Person x, Person y)
          {
            if (x == null && y == null) return 0;
            if (x == null) return 1;
            if (y == null) return -1;
            switch (_compareType)
            {
              case PersonCompareType.FirstName:
                return string.Compare(x.FirstName, y.FirstName);
              case PersonCompareType.LastName:
                return string.Compare(x.LastName, y.LastName);
              default:
                throw new ArgumentException("unexpected compare type");
            }
          }
        }

现在,可以将一个PersonComparer对象传递给Array.Sort()方法的第2个参数。下面按名字对persons数组排序(代码文件SortingSample/Program.cs):

        Array.Sort(persons, new PersonComparer(PersonCompareType.FirstName));
        foreach (var p in persons)
        {
          WriteLine(p);
        }

persons数组现在按名字排序:

        Ayrton Senna
        Damon Hill
        Graham Hill
        Niki Lauda

注意:Array类还提供了Sort方法,它需要将一个委托作为参数。这个参数可以传递给方法,从而比较两个对象,而不需要依赖IComparable或IComparer接口。第9章将介绍如何使用委托。