c#中空数组_C#之数组

@(Net)[C#|Array]

数组 是允许将多个数据项作为集合来处理的一种机制。CLR支持一维(如果此时数组索引从0开始,也被称为SZ(single-dimension,zero-base)、向量(Vector))、多维和交错数组(有数组构成的数组)。所有的数组类型都隐式的从System.Array抽象类派生,而后者又派生自System.Object。这意味着数组始终是引用类型,在托管堆上分配。在应用程序的变量或字段中,包含的是数组的引用,而不是包含数组本身的元素。

## C#数组的声明与初始化

当使用C#语法声明一个数组时,CLR会在内部将它转换为Array的子类——合成一个对应该数组维数和元素类型的伪类型。这个伪类型实现了类型化的泛型集合接口,如IList(可以查看IL代码看出),当初始化一个数组时,在托管堆上分配的内存除了数组元素,数组对象占据的内存块还包含一个类型对象指针、同步块索引和开销字段(overload,包括数组的秩、数组每一维的下限(称为数组的基,几乎总是0,称为0基数组)、每一维的长度、数组的元素类型等信息)。如果数组中的元素类型为引用类型,创建数组只是创建一组引用(初始值为null),此时并没有创建实际的对象。

在C#中,我们初始化数组的方式大致可以概括为两种,如下:

string[] s = new []{"hello","world"};//使用数组初始化器初始化一个数组(亦可只要大括号内部的内容)

var sa = new[]{"hello","world"};//使用隐式类型初始化一个数组,此时不能省略前面的new[]

除去上面静态创建的方式,还可以使用 Array.CreateInstance 方法在运行时动态的创建一个数组的实例,该方法需要制定数组中元素的类型及个数。

下面动态的创建一个长度为2的字符串数组。

Array arr = Array.CreateInstance(typeof(string), 2);

//赋值

arr.SetValue("hello", 0);

arr.SetValue("world", 1);

//取值

arr.GetValue(0);

//将其转换为静态数组

string[] cs_arr = (string[])arr;

## 一维0基数组

多数情况下,我们创建的数组都是0基数组(C#亦可以调用Array类的静态方法CreateInstance()创建非0基的数组,可以在运行时指定数组的元素类型、维数、和每一维开始的数组的下界,但这样的数组不符合CLS(Common Language Specification)),访问0基一维数组要比访问多维数组或非0基数组要快一点。这其中的原因有一下几方面:

1.   针对0基一维数据,具有一些特殊的IL指令,如 newarr(将新的0基一维数组的对象引用推送到堆栈上)、ldlen(将0基一维数组的数组元素的数目推送到计算堆栈上)、stelem(用计算堆栈中的值替换0基一维数组中指定索引位置的数组元素)、ldelema(将指定索引位置的数组元素的地址(托管指针)推送到计算堆栈的顶部)等,这些IL指令会导致JIT编译器生成优化代码。例如,JIT编译器生成的代码假定数组是0基的,那么在访问数组是就不需要从指定索引中减去一个偏移量了。

2.  这一方面我们举个例子来说明

int[] int_arr = {1,2,3,4,5};

for(int i=0;i

//执行对int_arr[i]的操作

}

在上述的for循环表达式中调用了数组的 Length属性,但我们知道实际上是调用的查询长度的方法(C#中的属性最终是以Set_propertyName和Get_propertyName的形式存在的,查询IL可以看出),JIT编译器知道Length是Array类的属性,所以在生成的代码中实际上只调用了该属性一次,然后将结果存储到一个临时变量中,每次循环迭代检查的都是这个临时变量。这样就加快了JIT编译代码的速度。这在访问非0基数组和多维数组时,JIT编译器不会讲索引检查从循环迭代中拿出来,索引每次都要验证指定的索引,具体来说就是生成类似如下的代码来检查(0>=arr.GetLowerBound(int dimensino))&&((Length-1)<=arr.GetUpperBound(int dimension))是否成立。如果很关心性能的话,使用交错数组(数组构成的数组)来代替矩形数组(多维数组)是个不错的选择。

数组实现的集合访问接口

所有的C#数组都实现了IEnumerable、IList、ICollection(注意这里实现的是非泛型类型的集合操作接口),这三个接口的定义如下:

IEnumerable:

public interface IEnumerable

{

IEnumerator GetEnumerator();

}

实现了IEnumerable接口的类支持枚举

IList:

public interface IList : ICollection, IEnumerable

{

int Add(object value);

void Clear();

bool Contains(object value);

int IndexOf(object value);

void Insert(int index, object value);

void Remove(object value);

void RemoveAt(int index);

}

实现IList接口的类支持基于索引的操作,在Array中,这些方法都是显式实现的,因此,如果一个数组想要调用其中的某个方法,必须转换为IList接口类型。

public interface ICollection:IEnumerable{

void CopyTo(Array arr,int index);

int Count{get;}

}

实现了ICollectiond的类具有Count属性,因其还继承自IEnumerable,故还可枚举。

前面我们说过Array类并没有实现上述集合接口对应的泛型版本,但当我们创建一个数组时,只要该数组中包含的元素为引用类型的,CLR会自动的为其数组类型及其所有基类实现IEnumerable、ICollection、IList接口,下面我们举例说明。

现有A和B两个类,其中B是A的子类,As和Bs是类型A、B对应的类型数组,则:

Console.WriteLine(Bs is IList);//True

Console.WriteLine(Bs is ICollection);//True

Console.WriteLine(Bs is IEnumerable);//True

Console.WriteLine(As is IList);//True

Console.WriteLine(As is ICollection);//True

Console.WriteLine(As is IEnumerable);//True

但如果数组中包含的元素是值类型的,则不会为其基类实现接口对应的泛型版本(所有的值类型都继承自System.ValueType和System.Object)

数组方法

#### 1.查找

快速搜索排序数组中的特定元素(一维数组)

1.public static int IndexOf(Array array, object value)

在数组array中查找指定元素value,如果存在就返回其索引值,如果不存在就返回-1.,该方法还有多个重载,可以指定查找开始的索引(startIndex)和查找的数组元素的个数(count),在这里必须满足startIndex - count+1>=0,否则会出现System.ArgumentOutOfRangeException` 的异常。

2.public static int LastIndexOf(Array array,object value)

作用和用法与IndexOf一样,只不过LastIndexOf的查找是从后往前查的。

搜索未排序数组汇总特定的元素

1.public static T Find (T[] array,Predicate match)

在数组array中查找匹配条件match的第一个数组元素并返回,如果不存在满足匹配条件的项,就返回T类型的默认值。如果要返回满足匹配条件的所有项,可以使用 *** public static T[] FindAll(T[] array, Predicate match)***,参数中的Predicate表示一个具有一个T类型参数返回类型为bool的泛型委托。如果不存在则返回一个长度为0的T[].

string[] test = {"hello","girl","I","Love","you","and","you","?"};

string[] result = Array.FindAll(test,s=>s.Contain("o"));//找出所有包含o的字符串

2.*public static T FindLast (T[] array,Predicate match)*的功能和用法与Find相同,只不过是从后向前查找的,既返回最后一个匹配条件的数组元素。

3.public static int FindIndex/FindLastIndex (T[] array, Predicate match)

在数组array中查找匹配条件mathc的第一个数组元素返回其索引值。如果不存在匹配条件的项就返回-1,该方法具有多个重载,重载的参数形式类似于IndexOf。

4.public static bool Exists(T[] array, Predicate match)

判断数组array中是否存在匹配条件的项,如果存在返回True,否则返回False(内部调用IndexOf方法实现)。

5.public static bool TrueForAll%lt;T>(T[] array, Predicate match)

判断数组array中的所有项是否都满足匹配条件match,如果是,返回true,否则,返回False。

6.public static int BinarySearch(T[] array,int index,int length,T value,IComparercomparer)

二分法查找,速度较快,但只适合于排序数组,而且要求元素能够比较排序该方法有多个重载,这个是参数最多的一个,其中array表示要搜索的要从零开始的一维排序数组,index表示要搜索的范围的起始索引。length表示要搜索的范围的长度,value表示要搜索的值,comparer表示比较元素时要使用的 IComparer实现。-若为 null,则使用每个元素的 IComparable实现。

2. 数组排序

Array内置以下排序方法:

public static void Sort(T[] array)

public static void Sort(Array array)

public static void Sort(Tkeys[] keys,TValue[] values)

public static void Sort(Array keys,Array values)

上述的每个方法重载后都可以接受:

1.int index  //排序开始的位置

2.int length  //排序的元素个数

3.*IComparer/IComparer  //判断排序的对象

4.Comparison  //执行排序的代码,只是一个Comparison类型的委托,该委托的定义如下:

public delegate int Comparison(T x,T y)

Array.Sort要求数组中的元素都实现IComparable接口(C#中的基元类型(能够被编译器直接识别的类型,C#中的每种基元类型都可以映射为FCL中的一种类型,如基元类型int对应着FCL中的System.Int32)都实现了该接口),,如果数组中的元素是不可比较的,或者希望重写默认的顺序比较,那么必须给Sort提供上述重载中的 3 或*4类型的参数。

下面我们简单举例说明一下它的用法:

接收一对数组的方法

int[] numbers = {3,2,1};

string[] words = {"three","two","one"};

Array.Sort(numbers,words);

执行完Sort方法后,numbers变为{1,2,3},words变为{"one","two","three"},这种排序方法实际上是通过调整每个数组的元素及基于第一个数组的顺序判断而实现的。

注意:Array.Sort方法的排序是在原数组上操作的,作为Sort的替代方法也可以使用LINQ(Language Integrated Query,集成语言查询)的OrderBy和ThenBy方法来实现,但LINQ的方法不会修改原数组,而是将排序结果保存在一个新的IEnumerable序列中返回。

#### 3. 倒转数组元素

通过Array的Reverse方法可以倒转数组中的所有或部分元素的顺序,该方法的定义如下:

public static void Reverse(Array array);

public static void Reverse(Array array,int index,int length);

#### 4. 数组的复制

Array有4个方法可以进行浅拷贝操作(如果要进行深拷贝必须进行遍历,自行复制):Clone、CopyTo、Copy和ConstrainedCopy,前两个方法时实例方法,后两个是静态方法。

Clone方法返回一个全新的数组(其实返回的是一个object,需要自行进行类型转换),Copy和CopyTo方法复制数组中的若干连续元素(这两个方法都有多个重载,可以指定源数组复制开始位置的索引(sourceIndex),要复制的元素个数(length),目标数组中存储开始位置的索引(destinationIndex))

**注意 *** 复制多维数组需要将多维索引映射为线性索引,例如,一个3x3数组的中间矩阵(position[1,1]),可以用4(13+1)来表示。

ConstraindeCopy执行一个原子操作:如果所有请求的元素都无法成功复制,那么操作就会回滚。

int[] a={1,2,3,4,5};

int[] b = {6,7,8,9,10};

//Array.Copy(sourceArray,sourceIndex,destinationIndex,destinationIndex,length)

Array.Copy(a,1,b,0,4);//此时数组b变为{2,3,4,5,10}

#### 5. 转换和调整大小

Array.ConvertAll会创建和返回一个包含元素类型OutPut类型的新数组,调用所提供的Converter代理(说白了就是一个委托,参数为TInput,返回值为TOutput)就可以复制元素,该方法的定义如下:

`public static TOutput[] ConvertAll(TInput[] array,Converterconverter)`

```C#

string[] s = {"1","2","3","4"};

var b = Array.ConvertAll(s,a=>Int32.Parse(a));//将数组s中的所有字符串都转换为Int32

Console.WriteLine(b.GetType());//System.Int32[]

var c = Array.ConvertAll(b,d=>d*d);//c为{1,4,9,16}

```

Array的Resize方法可以改变数组的长度,实现方式其实是创建一个指定长度的新数组,然后将对应长度个数的元素复制到新数组中,在通过引用参数返回这个新数组。然而在其他对象中指向原始数组的任意引用都不变,该方法的定义如下:

public static void Resize(ref T[] array,int newSize)

6. 两个数组的比较

假如现在有两个数组,如下示:

```cs

object[] a1 = {1,"2",true};

object[] a2 = {1,"2",true};

```

若要判断这两个数组是否相等该如何判断呢?你是否能够判断出下面的比对结果

bool eq1 = a1==a2;

bool eq2 = object.ReferenceEquals(a1,a2);

bool eq3 = a1.Equals(a2);

上面的三个比较的结果均为 false。那是因为数组本身是引用类型的,上面的三种方式比较的均为引用对象在堆栈中的地址,明显是不相同的。

那么,要比较两个数组是否完全相同,除了遍历外,还有其它的方式吗?答案是肯定的,其方式如下示:

IStructuralEquatable se1 = a1 as IStructuralEquatable;

if(se1!=null)

{

bool eq4 = se1.Equals(a2,StructuralComparisons.StructuralEqualityComparer));//true

}

<think>嗯,用户问的是关于C#中空初始值设定项、参数组未指定绑定导致的报错问题。首先,我需要回忆一下相关的错误情况。根据引用[1]中的内容,字段初始化的时候不能引用非静态成员,这可能导致错误。比如在类的字段初始化时如果调用了实例方法或者使用了实例变量,就会报错。这时候需要将初始化代码移到构造函数里。 然后,用户提到参数组未指定绑定的问题。这可能涉及到方法参数中的params关键字,或者可能是MVC中的模型绑定问题。如果是params数组,当没有传递参数时,数组会是的,但如果方法内部没有正确处理,可能会出错。需要检查方法是否允许参数,或者是否在调用时至少传递了一个参数。 另外,初始值设定项可能是指对象初始化器为的情况,比如尝试初始化一个集合但没提供元素。这时候可能需要使用集合的实例,比如new List<T>()而不是null。 还要考虑ASP.NET Core中的模型绑定问题,比如控制器方法期望的参数未被正确传递,导致绑定失败。这时候可能需要检查路由设置、参数名称是否匹配,或者使用[FromBody]等特性来明确绑定来源。 综合这些可能性,解决方案可能包括:将字段初始化移到构造函数,确保params数组正确处理值,检查模型绑定配置,以及使用合适的集合初始化。需要分步骤解释每个情况的具体解决方法和示例代码,帮助用户逐步排查问题。</think>### 解决方案:C#初始值设定项与参数组未指定绑定报错分析 #### 1. **初始值设定项报错** 当出现`字段初始值设定项无法引用非静态字段、方法或属性`的错误时,主要原因是**在类字段初始化阶段直接调用了实例成员**。根据引用[1]的说明,字段初始化阶段只能使用静态成员或常量值,此时类的实例尚未完全创建。 **解决方法:** - 将需要引用实例成员的初始化操作移至构造函数 - 使用静态成员或常量进行初始化 **示例代码:** ```csharp class MyClass { private List<int> myList; // 错误写法:= GetList(); // 正确方式 public MyClass() { myList = GetList(); // 在构造函数中初始化 } private List<int> GetList() => new List<int>(); } ``` #### 2. **参数组未指定绑定报错** 当涉及`params`参数组或模型绑定时,可能出现`未指定绑定参数`错误,常见于: - 使用`params object[]`参数但未传递任何参数 - ASP.NET Core模型绑定失败 **解决方法:** **(a) 普通方法参数处理** ```csharp void MyMethod(params string[] args) { if (args == null || args.Length == 0) { // 处理参数情况 } } ``` **(b) ASP.NET Core模型绑定** ```csharp [ApiController] public class MyController : Controller { [HttpPost] public IActionResult Create([FromBody] MyModel model) { // 明确绑定来源 if (model == null) return BadRequest(); // ... } } ``` #### 3. **集合初始化器优化** 当使用集合初始化时,推荐显式创建实例: ```csharp List<string> emptyList = new List<string>(); // 优于 null Dictionary<int, string> emptyDict = new Dictionary<int, string>(); ``` #### 4. **特殊场景:委托初始化** 当涉及函数指针或委托初始化时,需注意类型转换(参考引用[2]): ```csharp Action action = () => { /* 委托初始化 */ }; ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值