数组(System.Array)
作者:追风剑情 发布于:2021-3-8 9:38 分类:C#
本章内容:
初始化数组元素
● 数组转型
● 所有数组都隐式派生自 System.Array
● 所有数组都隐式实现IEnumerable,ICollection 和 IList
● 数组的传递和返回
● 创建下限非零的数组
● 数组的内部工作原理
● 不安全的数组访问和固定大小的数组
数组是允许将多个数据项作为集合来处理的机制。CLR 支持一维、多维和交错数组(即数组)构成的数组)。所有数组类型都隐式地从 System.Array 抽象类派生,后者又派生自System.Object。这意味着数组始终是引用类型,是在托管堆上分配的。在应用程序的变量或字段中,包含的是对数组的引用,而不是包含数组本身的元素。下面的代码更清楚地说
明了这一点:
Int32[] myIntegers; // 声明一个数组引用
myIntegers = new Int32[100];// 创建含有 100 个 Int32 的数组
第一行代码声明 myIntegers 变量,它能指向包含 Int32 值的一维数组。myIntegers 刚开始被设为 null,因为当时还没有分配数组。第二行代码分配了含有 100 个 Int32 值的数组,所有 Int32 都被初始化为 0。由于数组是引用类型,所以会在托管堆上分配容纳 100 个未装箱 Int32 所需的内存块。实际上,除了数组元素,数组对象占据的内存块还包含一个类型对象指针、一个同步块索引和一些额外的成员(这是额外的成员称为overhead字段或者说“开销字段”)。该数组的内存块地址被返回并保存到myIntegers 变量中。
多维数组
// 创建一个三维数组,由String引用构成
String[,,] myStrings = new String[5, 3, 10];
交错数组(jagged array)
数组构成的数组。
// 创建由多个Point数组构成的一维数组
Point[][] myPolygons = new Point[3][];
示例——初始化数组
// 下面语句等效 String[] names = new String[] {"Aidan", "Grant"}; String[] names = {"Aidan", "Grant"}; var names = new String[] {"Aidan", "Grant"}; var names = new[] {"Aidan", "Grant"}; //var names = {"Aidan", "Grant"}; // 错误
示例——匿名类型数组
// 使用C#的隐式类型的局部变量、隐式类型的数组和匿名类型功能 var kids = new [] {new { Name="Aidan" }, new { Name="Grant" }}; foreach (var kid in kids) Console.WriteLine(kid.Name);
数组转型
示例——数组转型
// 创建二维FileStream数组 FileStream[,] fs2dim = new FileStream[5, 10];// 隐式转型为二维Object数组 Object[,] o2dim = fs2dim;// 显式转型为二维Stream数组 Stream[,] s2dim = (Stream[,]) o2dim;// 创建一维Int32数组(元素是值类型) Int32 ildim = new Int32[5]; //错误: 不能将值类型的数组转型为其他任何类型 //Object[] oldim = (Object[]) ildim;// 创建一个新数组,使用Array.Copy将源数组中的每个元素 // 转型为目标数组中的元素,并把它们复制过去。 // 下面的代码创建元素为引用类型的数组, // 每个元素都是对已装箱Int32的引用 // Array.Copy是浅复制 Object[] obldim = new Object[ildim.Length]; Array.Copy(ildim, obldim, ildim.Length);
Array.Copy 的作用不仅仅是将元素从一个数组复制到另一个。Copy 方法还能正确处理内存的重叠区域,就像C的 memmove 函数一样。有趣的是,C的 memcpy 函数反而不能正确处理重叠的内存区域。Copy 方法还能在复制每个数组元素时进行必要的类型转换,具体如下所述:
● 将值类型的元素装箱为引用类型的元素,比如将一个Int32[]复制到一个Object[]中。
● 将引用类型的元素拆箱为值类型的元素,比如将一个Object[]复制到一个Int32[]中。
● 加宽 CLR 基元值类型,比如将一个Int32的元素复制到一个Double[]中。
● 在两个数组之间复制时,如果仅从数组类型证明不了两者的兼容性,比如从 Object[]转型为 IFormattable[],就根据需要对元素进行向下类型转换。如果Object[]中的每个对象都实现了 IFormattable,Copy 方法就能成功执行。
示例——Array.Copy
// 定义实现了一个接口的值类型 internal struct MyValueType : IComparable { public Int32 CompareTo(Object obj) { } } public static class Program { public static void Main() { MyValueType[] src = new MyValueType[100]; IComparable[] dest = new IComparable[src.Length];//初始化IComparable数组中的元素, //使它们引用源数组元素的已装箱版本 Array.Copy(src, dest, src.Length); } }
有时确实需要将数组从一种类型转换为另一种类型。这种功能称为数组协变性(array covariance)。但在利用它时要清楚由此而来的性能损失。
要将一个数组的元素可靠地复制到另一个数组,应该使用 System.Array 的 ConstrainedCopy 方法。该方法要么完成复制,要么抛出异常,总之不会破坏目标数组中的数据。这就允许 ConstrainedCopy 在约束执行区域(Constrained Execution Region,CER)中执行。为了提供这种保证,ConstrainedCopy 要求源数组的元素类型要么与目标数组的元素类型相同,要么派生自目标数组的元素类型。另外,它不执行任何装箱、拆箱或向下类型转换。
“按位兼容”因为英文原文是 bitwise-compatible,所以人们发明了 blittable 一词来表示这种类型。这 种类型在托管和非托管内存中具有相同的表示。一部分基元类型是 blittable 类型。如果一维数组包含 的是 blittable 类型,该数组也是 blittable 类型。另外,格式化的值类型如果只包含 blittable 类型,该值 类型也是 blittable 类型。欲知评情。请在MSDN文档中搜索"可直接复制到本机结构中的类型和非直接复制到本机结构中的类型"这一主题。——译注
所有数组都隐式派生自System.Array
所有数组都隐式实现IEnumerable, ICollection和IList
创建一维0基数组类型时,CLR自动使数组类型实现IEnumerable<T>, ICollection<T>和IList<T>(T是数组元素类型)。同时,还为数组类型的所有基类型实现这三个接口,只要它们是引用类型。
注意,如果数组包含值类型的元素,数组类型不会为元素的基类型实现接口。例如,如果执行以下代码:
DateTime[] dtArray; // 一个值类型的数组
那么,DateTime[]类型只会实现IEnumerable<DateTime>, ICollection<DateTime>和IList<DateTime>接口,不会为DateTime的基类型(包括System.ValueType和System.Object)实现这些泛型接口。这是因为值类型的数组在内存中的布局与引用类型的数组不同。
创建下限非零的数组
- using System;
- namespace ConsoleApp32
- {
- class Program
- {
- static void Main(string[] args)
- {
- Int32[] lowerBounds = { 2005, 1 };
- Int32[] lengths = { 5, 4};
- Decimal[,] quarterlyRevenue = (Decimal[,])
- Array.CreateInstance(typeof(Decimal), lengths, lowerBounds);
- Console.WriteLine("{0,4} {1,9} {2,9} {3,9} {4,9}",
- "Year", "Q1", "Q2", "Q3", "Q4");
- //获取第1维第1个元素的索引
- Int32 firstYear = quarterlyRevenue.GetLowerBound(0);
- //获取第1维最后一个元素的索引
- Int32 lastYear = quarterlyRevenue.GetUpperBound(0);
- //获取第2维第1个元素的索引
- Int32 firstQuarter = quarterlyRevenue.GetLowerBound(1);
- //获取第2维最后一个元素的索引
- Int32 lastQuarter = quarterlyRevenue.GetUpperBound(1);
- for (Int32 year = firstYear; year <= lastYear; year++)
- {
- Console.Write(year + " ");
- for (Int32 quarter = firstQuarter; quarter <= lastQuarter; quarter++)
- {
- Console.Write("{0,9:C} ", quarterlyRevenue[year, quarter]);
- }
- Console.WriteLine();
- }
- Console.ReadLine();
- }
- }
- }

访问一维0基数组的元素比访问非0基一维或多维数组的元素稍快。
以下代码演示了访问二维数组的三种方式(安全、交错和不安全)
- using System;
- using System.Diagnostics;
- namespace ConsoleApp32
- {
- class Program
- {
- private const Int32 c_numElements = 10000;
- static void Main(string[] args)
- {
- // 声明二维数组
- Int32[,] a2Dim = new int[c_numElements, c_numElements];
- // 将二维数组声明为交错数组(向量构成的向量)
- Int32[][] aJagged = new Int32[c_numElements][];
- for (Int32 x = 0; x < c_numElements; x++)
- aJagged[x] = new Int32[c_numElements];
- // 1: 用普通的安全技术访问数组中的元素
- Safe2DimArrayAccess(a2Dim);
- // 2: 用交错数组技术访问数组中的元素
- SafeJaggedArrayAccess(aJagged);
- // 3: 用unsafe技术访问数组中的所有无线
- Unsafe2DimArrayAccess(a2Dim);
- Console.ReadLine();
- }
- private static Int32 Safe2DimArrayAccess(Int32[,] a)
- {
- Int32 sum = 0;
- for (Int32 x = 0; x < c_numElements; x++)
- {
- for (Int32 y = 0; y < c_numElements; y++)
- sum += a[x, y];
- }
- return sum;
- }
- private static Int32 SafeJaggedArrayAccess(Int32[][] a)
- {
- Int32 sum = 0;
- for (Int32 x = 0; x < c_numElements; x++)
- {
- for (Int32 y = 0; y < c_numElements; y++)
- sum += a[x][y];
- }
- return sum;
- }
- //C#编译器需指定/unsafe开关 ([项目属性页]->[生成]->允许不安全代码)
- //访问标记了unsafe修饰符,这是fixed语句所必须的。
- private static unsafe Int32 Unsafe2DimArrayAccess(Int32[,] a)
- {
- Int32 sum = 0;
- fixed (Int32* pi = a)
- {
- for (Int32 x = 0; x < c_numElements; x++)
- {
- Int32 baseOfDim = x * c_numElements;
- for (Int32 y = 0; y < c_numElements; y++)
- sum += pi[baseOfDim + y];
- }
- }
- return sum;
- }
- }
- }
Unsafte2DimArrayAccess方法标记了 unsafe 修饰符,这是使用 C#的 fixed 语句所必须的。编译这段代码要求在运行 C#编译器时指定/unsafe 开关,或者在 Microsoft Visual Studio 的项目属性页的“生成”选项卡中勾选“允许不安全代码”。
写代码时,不安全数据访问技术有时或许是你的最佳选择,但要注意该技术的三处不足。
● 相较于其他技术,处理数组元素的代码更复杂,不容易读和写,因为要使用 C# fixed 语句,要执行内存地址计算。
● 计算过程中出错,可能访问到不属于数组的内存。这会造成计算错误,损坏内存数据,破坏类型安全性,并可能造成安全漏洞。
● 因为这些潜在的问题,CLR 禁止在降低了安全级别的环境(如 Microsoft Silverlight)中运行不安全代码。
不安全的数组访问和固定大小的数组
不安全的数组访问非常强大,因为它允许访问以下元素。
● 堆上的托管数组对象中的元素(上一节对此进行了演示)。
● 非托管堆上的数组中的元素。SecureString 示例演示了如何调用 System.Runtime.InteropServices.Marshal 类的 SecureStringToCoTaskMemUnicode 方法来返回一个数组,并对这个数组进行不安全的数组访问。
● 线程栈上的数组中的元素。
如果性能是首要目标,请避免在堆上分配托管的数组对象。相反,在线程栈上分配数组。这是通过C#的 stackalloc 语句来完成的(它在很大程度上类似于C的 alloca 函数), stackalloc语句只能创建一维 0 基、由值类型元素构成的数组,而且值类型绝对不能包含任何引用类型的字段。实际上,应该把它的作用看成是分配一个内存块,这个内存块可以使用不安全的指针来操纵。所以,不能将这个内存缓冲区的地址传给大部分 FCL 方法。当然,栈上分配的内存(数组)会在方法返回时自动释放;这对增强性能起了一定作用。使用这个功能要求为 C#编译器指定/unsafe 开关。
以下代码演示了如何使用C# stackalloc 语句
- using System;
- namespace ConsoleApp32
- {
- class Program
- {
- static void Main(string[] args)
- {
- StackallocDemo();
- InlineArrayDemo();
- Console.ReadLine();
- }
- private static void StackallocDemo()
- {
- unsafe
- {
- const Int32 width = 20;
- Char* pc = stackalloc Char[width]; // 在栈上分配数组
- String s = "Jeffrey Richter"; // 15个字符
- for (Int32 index = 0; index < width; index++)
- {
- pc[width - index - 1] = (index < s.Length) ? s[index] : '.';
- }
- // 下面这行代码显示".....rethciR yerffeJ"
- Console.WriteLine(new String(pc, 0, width));
- }
- }
- private static void InlineArrayDemo()
- {
- unsafe
- {
- CharArray ca; // 在栈上分配数组
- Int32 widthInBytes = sizeof(CharArray);
- Int32 width = widthInBytes / 2;
- String s = "Jeffrey Richter"; // 15个字符
- for (Int32 index = 0; index < width; index++)
- {
- ca.Characters[width - index - 1] =
- (index < s.Length) ? s[index] : '.';
- }
- // 下面这行代码显示".....rethciR yerffeJ"
- Console.WriteLine(new String(ca.Characters, 0, width));
- }
- }
- }
- internal unsafe struct CharArray
- {
- // 这个数组内联(嵌入)到结构中
- public fixed Char Characters[20];
- }
- }

通常,由于数组是引用类型,所以结构中定义的数组字段实际只是指向数组的指针或引用;数组本身在结构的内存的外部。不过,也可像上述代码中的 CharArray结构那样,直接将数组嵌入结构。在结构中嵌入数组需满足以下几个条件。
● 类型必须是结构(值类型);不能在类(引用类型)中嵌入数组。
● 字段或其定义结构必须用 unsafe 关键字标记。
● 数组字段必须用 fixed 关键字标记。
● 数组必须是一维 0 基数组。
● 数组的元素类型必须是以下类型之一:Boolean,Char,SByte,Byte, Int16,Int32, UIntl6,UInt32,Int64,UInt64,Single 或 Double。
要和非托管代码进行互操作,而且非托管数据结构也有一个内联数组,就特别适合使用内联的数组。但内联数组也能用于其他地方。上述代码中的 InlineArrayDemo 方法提供了如何使用内联数组的一个例子。它执行和 StackallocDemo 方法一样的功能,只是用了不一样的方式。
标签: C#
日历
最新文章
随机文章
热门文章
分类
存档
- 2025年3月(4)
- 2025年2月(3)
- 2025年1月(1)
- 2024年12月(5)
- 2024年11月(5)
- 2024年10月(5)
- 2024年9月(3)
- 2024年8月(3)
- 2024年7月(11)
- 2024年6月(3)
- 2024年5月(9)
- 2024年4月(10)
- 2024年3月(11)
- 2024年2月(24)
- 2024年1月(12)
- 2023年12月(3)
- 2023年11月(9)
- 2023年10月(7)
- 2023年9月(2)
- 2023年8月(7)
- 2023年7月(9)
- 2023年6月(6)
- 2023年5月(7)
- 2023年4月(11)
- 2023年3月(6)
- 2023年2月(11)
- 2023年1月(8)
- 2022年12月(2)
- 2022年11月(4)
- 2022年10月(10)
- 2022年9月(2)
- 2022年8月(13)
- 2022年7月(7)
- 2022年6月(11)
- 2022年5月(18)
- 2022年4月(29)
- 2022年3月(5)
- 2022年2月(6)
- 2022年1月(8)
- 2021年12月(5)
- 2021年11月(3)
- 2021年10月(4)
- 2021年9月(9)
- 2021年8月(14)
- 2021年7月(8)
- 2021年6月(5)
- 2021年5月(2)
- 2021年4月(3)
- 2021年3月(7)
- 2021年2月(2)
- 2021年1月(8)
- 2020年12月(7)
- 2020年11月(2)
- 2020年10月(6)
- 2020年9月(9)
- 2020年8月(10)
- 2020年7月(9)
- 2020年6月(18)
- 2020年5月(4)
- 2020年4月(25)
- 2020年3月(38)
- 2020年1月(21)
- 2019年12月(13)
- 2019年11月(29)
- 2019年10月(44)
- 2019年9月(17)
- 2019年8月(18)
- 2019年7月(25)
- 2019年6月(25)
- 2019年5月(17)
- 2019年4月(10)
- 2019年3月(36)
- 2019年2月(35)
- 2019年1月(28)
- 2018年12月(30)
- 2018年11月(22)
- 2018年10月(4)
- 2018年9月(7)
- 2018年8月(13)
- 2018年7月(13)
- 2018年6月(6)
- 2018年5月(5)
- 2018年4月(13)
- 2018年3月(5)
- 2018年2月(3)
- 2018年1月(8)
- 2017年12月(35)
- 2017年11月(17)
- 2017年10月(16)
- 2017年9月(17)
- 2017年8月(20)
- 2017年7月(34)
- 2017年6月(17)
- 2017年5月(15)
- 2017年4月(32)
- 2017年3月(8)
- 2017年2月(2)
- 2017年1月(5)
- 2016年12月(14)
- 2016年11月(26)
- 2016年10月(12)
- 2016年9月(25)
- 2016年8月(32)
- 2016年7月(14)
- 2016年6月(21)
- 2016年5月(17)
- 2016年4月(13)
- 2016年3月(8)
- 2016年2月(8)
- 2016年1月(18)
- 2015年12月(13)
- 2015年11月(15)
- 2015年10月(12)
- 2015年9月(18)
- 2015年8月(21)
- 2015年7月(35)
- 2015年6月(13)
- 2015年5月(9)
- 2015年4月(4)
- 2015年3月(5)
- 2015年2月(4)
- 2015年1月(13)
- 2014年12月(7)
- 2014年11月(5)
- 2014年10月(4)
- 2014年9月(8)
- 2014年8月(16)
- 2014年7月(26)
- 2014年6月(22)
- 2014年5月(28)
- 2014年4月(15)
友情链接
- Unity官网
- Unity圣典
- Unity在线手册
- Unity中文手册(圣典)
- Unity官方中文论坛
- Unity游戏蛮牛用户文档
- Unity下载存档
- Unity引擎源码下载
- Unity服务
- Unity Ads
- wiki.unity3d
- Visual Studio Code官网
- SenseAR开发文档
- MSDN
- C# 参考
- C# 编程指南
- .NET Framework类库
- .NET 文档
- .NET 开发
- WPF官方文档
- uLua
- xLua
- SharpZipLib
- Protobuf-net
- Protobuf.js
- OpenSSL
- OPEN CASCADE
- JSON
- MessagePack
- C在线工具
- 游戏蛮牛
- GreenVPN
- 聚合数据
- 热云
- 融云
- 腾讯云
- 腾讯开放平台
- 腾讯游戏服务
- 腾讯游戏开发者平台
- 腾讯课堂
- 微信开放平台
- 腾讯实时音视频
- 腾讯即时通信IM
- 微信公众平台技术文档
- 白鹭引擎官网
- 白鹭引擎开放平台
- 白鹭引擎开发文档
- FairyGUI编辑器
- PureMVC-TypeScript
- 讯飞开放平台
- 亲加通讯云
- Cygwin
- Mono开发者联盟
- Scut游戏服务器引擎
- KBEngine游戏服务器引擎
- Photon游戏服务器引擎
- 码云
- SharpSvn
- 腾讯bugly
- 4399原创平台
- 开源中国
- Firebase
- Firebase-Admob-Unity
- google-services-unity
- Firebase SDK for Unity
- Google-Firebase-SDK
- AppsFlyer SDK
- android-repository
- CQASO
- Facebook开发者平台
- gradle下载
- GradleBuildTool下载
- Android Developers
- Google中国开发者
- AndroidDevTools
- Android社区
- Android开发工具
- Google Play Games Services
- Google商店
- Google APIs for Android
- 金钱豹VPN
- TouchSense SDK
- MakeHuman
- Online RSA Key Converter
- Windows UWP应用
- Visual Studio For Unity
- Open CASCADE Technology
- 慕课网
- 阿里云服务器ECS
- 在线免费文字转语音系统
- AI Studio
- 网云穿
- 百度网盘开放平台
- 迅捷画图
- 菜鸟工具
- [CSDN] 程序员研修院
- 华为人脸识别
- 百度AR导航导览SDK
- 海康威视官网
- 海康开放平台
- 海康SDK下载
- git download
- Open CASCADE
- CascadeStudio
交流QQ群
-
Flash游戏设计: 86184192
Unity游戏设计: 171855449
游戏设计订阅号
