Array类是所有一维和多维数组的隐式基类,同时也是实现标准集合接口的最基本的类型。Array类实现了类型统一,因此它为所有数组提供了一组通用的方法,不论这些数组元素的类型,这些通用的方法均适用。
正因为数组如此重要,所以C#为声明数组和初始化数组提供了明确的语法。在使用C#语法声明一个数组时,CLR隐式地构建Array类--合成一个伪类型以匹配数组的维数和数组元素的类型。而且这个伪类型实现了generic集合接口,比如IList<string>接口。
CLR在创建数组类型实例时会做特殊处理--在内存中为数组分配连续的空间。这就使得索引数组非常高效,但这却阻止了对数组的修改或调正数组大小。
Array类实现了IList<T>接口和IList接口。Array类显示地实现了IList<T>接口,这是为了保证接口的完整性。但是在固定长度集合比如数组上调用IList<T>接口的Add或Remove方法时,会抛出异常(因为数组实例一旦声明之后,就不能更改数组的长度)。Array类提供了一个静态的Resize方法,使用这个方法创建一个新的数组实例,然后复制当前数组的元素到新的实例。此外,在程序中,当在任何地方引用一个数组都会执行最原始版本的数组实例。因此如果希望集合大小可以调整,那么你最好使用List<T>类。
数组元素可以是值类型也可以是引用类型。值类型数组元素直接存在数组中,比如包含3个整数的数组会占用24个字节的连续内存。而引用类型的数组元素,占用的空间和一个引用占用的空间一样(32位环境中4个字节,64为环境中8个字节)。我们先来看下面的代码:
int[] numbers =new int[3];numbers[0] =1;numbers[1] = 7;StringBuilder[] builders = new StringBuilder[5];builders[0] = new StringBuilder("Builder1");builders[1] = new StringBuilder("Builder2");builders[2] = new StringBuilder("Builder3");
对应的内存分配变化如下面几张图所示:
执行完int[] numbers=new int[3]之后,在内存中分配了8×3=24个字节,每个字节都为0。
执行完numbers[0]=1之后,数组声明后的第一个8字节变为00 00 00 01。
同样地,执行完numbers[1]=7之后,第二段8个字节变为00 00 00 07。
对应引用类型的数组,我们用一张图来说明内存分配:
看起来分复杂,其实内存分配示意图如下
通过Clone方法可以克隆一个数组,比如arrayB = arrayA.Clone()。但是,克隆数组执行浅拷贝,也就是说数组自己包含的那部分内容才会被克隆。简单说,如果数组包含的是值类型对象,那么克隆了这些对象的值。而数组的子元素是引用类型,那么仅仅克隆引用类型的地址。
StringBuilder[] builders2 = builders;ShtringBuilder[] shallowClone = (StringBuilder[])builders.Clone();
与之对应的内存分配示意图:
如果需要执行深拷贝,即克隆引用类型的子对象;那么你需要遍历数组并手动的克隆每个数组元素。深克隆规则也适用于.NET其他集合类型。
尽管数组在设计时,主要使用32位的索引,它也在一定程度上支持64位索引,这需要使用那些既接收Int32又接收Int64类型参数的方法。这些重载方法在实际中并没有意义,因为CLR不允许任何对象--包括数组在内--的大小超过2G(无论32位的系统还是64位的系统)
创建数组和索引数组的最简单方式就是通过C#语言的构建器
Int[] myArray={1,2,3};int first=myArray[0];int last = myArray[myArray.Length-1];
或者,你可以通过调用Array.CreateInstance方法动态地创建一个数组实例。你可以通过这种方式指定数组元素的类型和数组的维度。而GetValue和SetValue方法允许你访问动态创建的数组实例的元素。
Array a = Arrat.CreateInstance(typeof(string), 2);a.SetValue('hi", 0);a.SetValue("there",1);string s = (string)a.getValue(0);string[] cSharpArray = (string[])a;string s2 = cSharpArray[0];
动态创建的零索引的数组可以转换为一个匹配或兼容的C#数组。比如,如果Apple是Fruit的子类,那么Apple[]可以转换成Fruit[]。这也是为什么object[]没有作为统一的数组类型,而是使用Array类;答案在于object[]不仅与多维数组不兼容,而且还与值类型数组不兼容。因此我们使用Array类作为统一的数组类型。
GetValue和SetValue对编译器生成的数组也起作用,若想编写一个方法处理任何类型的数组和任意维度的数组,那么这两个方法非常有用。对于多维数组,这两个方法可以把一个数组当作索引参数。
public object GetValue(params int[] indices)public void SetValue(object value, params int[] indices)
下面的方法在屏幕打印任意数组的第一个元素,无论数组的维度
void WriteFirstValue (Array a){Console.Write (a.Rank + "-dimensional; "); int[] indexers = new int[a.Rank];Console.WriteLine ("First value is " + a.GetValue (indexers));}void Demo(){int[] oneD = { 1, 2, 3 };int[,] twoD = { {5,6}, {8,9} };WriteFirstValue (oneD); // 1-dimensional; first value is 1WriteFirstValue (twoD); // 2-dimensional; first value is 5}
在使用SetValue方法时,如果元素与数组类型不兼容,那么会抛出异常。
无论采取哪种方式实例化一个数组之后,那么数组元素自动初始化了。对于引用类型元素的数组而言,初始化数组元素就是把null值赋给这些数组元素;而对于值类型数组元素,那么会把值类型的默认值赋给数组元素。此外,调用Array类的Clear方法也可以完成同样的功能。Clear方法不会影响数组大小。这和常见的Clear方法(比如ICollection<T>.Clear方法)不一样,常见的Clear方法会清除集合的所有元素。
通过foreach语句,可以非常方便地遍历数组:
int[] myArray = { 1, 2, 3};foreach (int val in myArray) Console.WriteLine (val);
你还可以使用Array.ForEach方法来遍历数组
public static void ForEach<T> (T[] array, Action<T> action);
该方法使用Action代理,此代理方法的签名是(接收一个参数,不返回任何值):
public delegate void Action<T> (T obj);
下面的代码显示了如何使用ForEach方法
Array.ForEach (new[] { 1, 2, 3 }, Console.WriteLine);
你可能会很好奇Array.ForEach是如何执行的,它就是这么执行的
public static void ForEach<T>(T[] array, Action<T> action) { if( array == null) { throw new ArgumentNullException("array"); } if( action == null) { throw new ArgumentNullException("action"); } Contract.EndContractBlock(); for(int i = 0 ; i < array.Length; i++) { action(array[i]); }}
在内部执行for循环,并调用Action代理。在上面的实例中,我们调用Console.WriteLine方法,所以可以在屏幕上输出1,2,3。
Array提供了下列方法或属性以获取数组的长度和数组的维度:
public int GetLength (int dimension);public long GetLongLength (int dimension);public int Length { get; }public long LongLength { get; }public int GetLowerBound (int dimension);public int GetUpperBound (int dimension);public int Rank { get; }GetLength和GetLongLength返回指定维度的长度(0表示一维数组),Length和LongLength返回数组中所有元素的总数(包含了所有维度)。
GetLowerBound和GetUpperBound对于多维数组非常有用。GetUpperBound返回的结果等于指定维度的GetLowerBound+指定维度的GetLength
Array类对外提供了一系列方法,以在一个维度中查找元素。比如:
如果没有找到指定的值,数组的这些搜索方法不会抛出异常。相反,搜索方法返回-1(假定数组的索引都是以0开始),或者返回generic类型的默认值(int返回0,string返回null)。
二进制搜索速度很快,但是它仅仅适用于排序后的数组,而且数组的元素是根据大小排序,而不是根据相等性排序。正因为如此,所以二进制搜索方法可以接收IComparer或IComparer<T>对象以对元素进行排序。传入的IComparer或IComparer<T>对象必须和当前数组所使用的排序比较器一致。如果没有提供比较器参数,那么数组会使用默认的排序算法。
IndexOf和LastIndexOf方法对数组进行简单的遍历,然后根据指定的值返回第一个(或最后一个)元素的位置。
以断定为基础(predicate-based)的搜索方法接受一个方法代理或lamdba表达式判断元素是否满足“匹配”。一个断定(predicate)是一个简单的代理,该代理接收一个对象并返回bool值:
public delegate bool Precicate<T>(T object);
下面的例子中,我们搜索字符数组中包含字母A的字符:
static void Main(string[] args){ string[] names = { "Rodney", "Jack", "Jill" }; string match = Array.Find(names, ContainsA); Console.WriteLine(match); Console.ReadLine();}static bool ContainsA(string name){ return name.Contains("a");}
上面的代码可以简化为:
static void Main(string[] args){ string[] names = { "Rodney", "Jack", "Jill" }; string match = Array.Find(names, delegate(string name) { return name.Contains("a"); }); Console.WriteLine(match); Console.ReadLine();}
如果使用lamdba表达式,那么代码还可以更简洁:
static void Main(string[] args){ string[] names = { "Rodney", "Jack", "Jill" }; string match = Array.Find(names, name=>name.Contains("a")); Console.WriteLine(match); Console.ReadLine();}
FindAll方法则从数组中返回满足断言(predicate)的所有元素。实际上,该方法等同于Enumerable.Where方法,只不过数组的FindAll是从数组中返回匹配的元素,而Where方法从IEnumerable<T>中返回。
如果数组成员满足指定的断言(predicate),那么Exists方法返回True,该方法等同于Enumerable.Any方法。
所以数组的所有成员都满足指定的断言(predicate),那么TrueForAll方法返回True,该方法等同于Enumerable.All方法。
数组有下列自带的排序方法:
public static voi
新闻热点
疑难解答