“推入” C#数组的最佳方法

Best way to “push” into C# array

早上好所有

所以我知道这是一个讨论比较广泛的问题,但是我似乎找不到确切的答案。我知道您可以使用列表执行我要的操作,但这不能解决我的问题。

我用初学者C#授课。我有PHP和Java Script的背景,因此倾向于从这些语言方面进行思考。

我一直在寻找向学生展示如何快速向数组中添加元素的最佳方法。我们不能使用列表,因为这些都不是我目前所教课程的一部分(尽管他们确实在请在学期晚些时候来。)

因此,我正在寻找执行Java array.push(newValue)类型场景的最简单方法,这对于新手程序员而言是可以理解的,而无需教他们不好的实践。

到目前为止,这是我一直在解决该问题的方式:

1
2
3
4
5
6
7
8
for (int i = 0; i < myArray.Length; i++)
{
   if(myArray[i] == null)
   {
       myArray[i] = newValue;
       break;
   }
}

我只需要知道这是否是可以接受的方法,是否在教给他们任何不良做法,或者是否有更好/更简单的方法来实现自己的目标。

编辑
问题的症结在于需要将元素添加到数组的第一个空插槽中,而这正是Java push函数可以做到的。

任何建议将不胜感激。


array.push类似于List<T>.Add。 .NET数组是固定大小的,因此您实际上无法添加新元素。您所能做的就是创建一个比原始数组大一个元素的新数组,然后设置最后一个元素,例如

1
2
Array.Resize(ref myArray, myArray.Length + 1);
myArray[myArray.GetUpperBound(0)] = newValue;

编辑:

对于此问题的编辑,我不确定该答案是否确实适用:

The crux of the matter is that the element needs to be added into the
first empty slot in an array, lie a Java push function would do.

我提供的代码有效地附加了一个元素。如果目标是设置第一个空元素,则可以执行以下操作:

1
2
3
4
5
6
int index = Array.IndexOf(myArray, null);

if (index != -1)
{
    myArray[index] = newValue;
}

编辑:

这里是扩展方法,它封装该逻辑并返回放置值的索引;如果没有空元素,则返回-1。请注意,此方法也适用于值类型,将具有该类型默认值的元素视为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class ArrayExtensions
{
    public static int Push< T >(this T[] source, T value)
    {
        var index = Array.IndexOf(source, default(T));

        if (index != -1)
        {
            source[index] = value;
        }

        return index;
    }
}


您的问题有点离题了。特别是,您说"该元素需要添加到数组的第一个空插槽中,这是Java推函数可以做到的。"

  • Java的Array没有推送操作-JavaScript有。 Java和JavaScript是两种截然不同的语言
  • JavaScript的push函数不能像您描述的那样运行。当您将值"推"入JavaScript数组时,该数组将扩展一个元素,并为该新元素分配推入的值,请参见:Mozilla的Array.prototype.push函数文档
  • 动词" Push "并不是我所知道的任何语言都可以与Array一起使用的语言,除了JavaScript。我怀疑它只存在于JavaScript中,因为它可能存在(因为JavaScript是一种完全动态的语言)。我很确定这不是故意设计的。

    可以用这种效率低下的方式编写C#中的JavaScript样式的Push操作:

    1
    2
    3
    4
    int [] myArray = new int [] {1, 2, 3, 4};
    var tempList = myArray.ToList();
    tempList.Add(5);
    myArray = tempList.ToArray();   //equiv: myArray.Push(5);

    " Push "用于某些类型的容器,尤其是Stacks,Queues和Deques(它们有两次推送-一次从前面,一次从后面)。我敦促您在对数组的解释中不要将Push作为动词包含在内。它对CS学生的词汇没有任何影响。

    在C#中,就像在大多数传统过程语言中一样,数组是包含在固定长度的连续内存块中的单一类型元素的集合。分配数组时,将分配每个数组元素的空间(并且在C#中,这些元素被初始化为该类型的默认值,对于引用类型,其值为null)。

    在C#中,引用类型的数组用对象引用填充;值类型的数组填充有该值类型的实例。结果,由4个字符串组成的数组使用与作为应用程序类的4个实例组成的数组相同的内存(因为它们都是引用类型)。但是,包含4个DateTime实例的数组比包含4个短整数的数组要长得多。

    在C#中,数组的实例是System.Array(引用类型)的实例。数组具有一些属性和方法(例如Length属性)。否则,您将无法对数组做很多事情:您可以使用数组索引从(或向)单个元素读取(或写入)。类型T的数组也实现IEnumerable<T>,因此您可以遍历数组的元素。

    数组是可变的(可以写入数组中的值),但是它们具有固定的长度-不能扩展或缩短。它们是有序的,并且不能重新排列(除非手动手动刷新值)。

    C#数组是协变的。如果您要问C#语言设计师,这将是他们最后悔的功能。这是打破C#类型安全性的几种方法之一。考虑以下代码(假设Cat和Dog类继承自Animal):

    1
    2
    3
    4
    Cat[] myCats = new Cat[]{myCat, yourCat, theirCat};
    Animal[] animals = (Animal[]) myCats;     //legal but dangerous
    animals[1] = new Dog();                   //heading off the cliff
    myCats[1].Speak();                        //Woof!

    该"功能"是由于.NET Framework的初始版本中缺少通用类型和显式协方差/协方差以及复制Java"功能"的冲动的结果。

    数组确实出现在许多核心.NET API(例如System.Reflection)中。它们再次出现在这里,因为初始版本不支持通用集合。

    通常,有经验的C#程序员不会在他的应用程序中使用很多数组,而是倾向于使用功能更强大的集合,例如List<T>Dictionary<TKey, TValue>HashSet<T>和朋友。尤其是,该程序员倾向于使用所有集合实现的接口IEnumerable<T>传递集合。使用IEnumerable<T>作为参数和返回类型(在可能和逻辑上)的最大优点是,通过IEnumerable<T>引用访问的集合是不可变的。这有点像在C中正确使用const

    在每个人都掌握了基础知识之后,您可能会考虑在数组讲课中添加新的Span<T>类型。跨度可以使C#数组有用。

    最后,LINQ(语言集成查询)为集合引入了许多功能(通过向IEnumerable<T>添加扩展方法)。确保您的学生的代码顶部没有using System.Linq;语句-将LINQ混入数组中刚开始的学生的类上会使他或她感到困惑。

    顺便说一句:您要教哪种班?在什么级别?


    如前所述,List提供了以干净的方式添加元素的功能,要对数组执行相同操作,则必须调整它们的大小以容纳额外的元素,请参见下面的代码:

    1
    2
    3
    4
    5
    6
    int[] arr = new int[2];
    arr[0] = 1;
    arr[1] = 2;
    //without this line we'd get a exception
    Array.Resize(ref arr, 3);
    arr[2] = 3;

    关于循环的想法:

    初始化数组时,将数组的

    元素设置为其默认值。因此,如果要在包含引用类型(其默认值为null)的数组中填充" blanks ",那么您的方法将是不错的。

    但是它不适用于值类型,因为它们是用0初始化的!


    C#在这方面与JavaScript有所不同。由于进行了严格的检查,因此您可以定义数组的大小,并且应该了解有关数组的所有信息,例如其边界,最后一项放置在何处以及哪些项未被使用。如果要调整其大小,应该将数组的所有元素复制到一个更大的新元素中。

    因此,如果您使用原始数组,则除了保持最后一个空的可用xx之外,别无其他方法,就像您已经在这样做的那样,在该索引处将可用的index项分配给该数组。 >

    但是,如果您希望运行时维护此信息并完全抽象出数组,但仍在下面使用数组,则C#提供了一个名为ArrayList的类来提供此抽象。

    ArrayList抽象一个Object的松散类型数组。在此处查看源代码。

    它处理所有问题,例如调整数组大小,维护最后可用的索引等。但是,它从您那里提取/封装了此信息。您只使用ArrayList

    要将任何类型的项目推入基础数组末尾的基础数组中,请像下面这样调用ArrayList上的Add方法:

    1
    2
    3
    4
    /* you may or may not define a size using a constructor overload */
    var arrayList = new ArrayList();

    arrayList.Add("Foo");

    编辑:有关类型限制的说明

    就像每种编程语言和运行时一样,C#将堆对象与那些不会出现在堆上并且只会保留在函数的参数堆栈中的对象进行分类。 C#通过名称"值类型"与"引用类型"指出了这一区别。其值在堆栈上的所有事物都称为值类型,而将在堆上的所有事物称为引用类型。这与JavaScript在对象和文字之间的区别大致相似。

    您可以将任何东西放入C#的ArrayList中,无论它是值类型还是引用类型。就无类型而言,这使它最接近JavaScript数组,尽管这三个数组(JavaScript数组,JavaScript语言和C#ArrayList)实际上都没有类型。

    因此,您可以将任何您想要的内容放入数字常量,字符串常量,组成的类的对象,布尔值,浮点数,双精度型,结构体中。 >

    这是因为ArrayList在内部维护并存储您放入其中的所有内容,并将其存储在Object s数组中,正如您在我的原始答案和链接的源代码中所指出的那样。

    然后,当您放置不是对象的东西时,C#将创建一个Object类型的新对象,并将您放入ArrayList中的东西的值存储到此新的Object类型对象中。此过程称为装箱,与JavaScript装箱机制非常不同。

    例如在JavaScript中,虽然您可以使用数字文字来调用Number对象上的函数,但是您不能在数字文字的原型中添加某些内容。

    1
    2
    3
    4
    5
    6
    // Valid javascript
    var s = 4.toString();

    // Invalid JavaScript code
    4.prototype.square = () => 4 * 4;
    var square = 4.square();

    就像JavaScript在对toString方法的调用中将数字文字4装箱一样,C#在将非对象的所有内容放入ArrayList时也将其包装为Object类型。

    1
    2
    3
    var arrayList = new ArrayList();

    arrayList.Add(4); // The value 4 is boxed into a `new Object()` first and then that new object is inserted as the last element in the `ArrayList`.

    这涉及一定的惩罚,就像JavaScript一样。

    在C#中,您可以避免这种损失,因为C#提供了ArrayList的强类型版本,称为List<T>。因此,您不能将任何内容放入List<T>;只是T类型。

    但是,我从您的问题文本中假定您已经知道C#具有用于强类型项目的通用结构。您的问题是要有一个像JavaScript这样的数据结构,表现出无类型性和弹性的语义,就像JavaScript Array对象一样。在这种情况下,ArrayList最接近。

    从您的问题中还可以清楚地看到,您的兴趣是学术上的,而不是在生产应用程序中使用该结构。

    因此,我假设对于生产应用程序,您已经知道通用/强类型数据结构(例如List<T>)比非类型数据结构(例如ArrayList)的性能更好。铅>


    这可以接受,因为它可以分配给数组。但是,如果您要推送,我很确定在数组中是不可能的。而是可以通过使用Stack,Queue或任何其他数据结构来实现。
    实数数组不具有此类功能。但是派生类(例如ArrayList)拥有它。


    在C#中没有array.push(newValue)。您不会使用C#推送到数组。我们为此使用的是List<T>。您可能要考虑的(仅用于教学目的)是ArrayList(没有泛型,它是一个IList,所以...)。

    1
    2
    3
    4
    5
    6
    7
    8
    static void Main()
    {
        // Create an ArrayList and add 3 elements.
        ArrayList list = new ArrayList();
        list.Add("One"); // Add is your push
        list.Add("Two");
        list.Add("Three");
    }


    除了将值分配给该数组的特定索引外,我认为没有其他方法。


    根据注释"那不是推送到数组。它只是分配给它"

    如果您正在寻找将值分配给数组的最佳实践,那么这是分配值的唯一方法。

    1
    Array[index]= value;

    只有当您不想使用List时,才可以分配值。


    有两种方法可以完成此操作。

    首先,转换为列表,然后再次转换为数组:

    1
    2
    3
    List<int> tmpList = intArry.ToList();
    tmpList.Add(anyInt);
    intArry = tmpList.ToArray();

    现在不建议您这样做,因为您转换为列表然后再次返回数组。如果您不想使用列表,则可以使用第二种将值直接分配给数组的方法:

    1
    2
    3
    4
    5
    int[] terms = new int[400];
    for (int runs = 0; runs < 400; runs++)
    {
        terms[runs] = value;
    }

    这是直接的方法,如果您不想纠缠列表和转化,
    建议您使用。


    我很抱歉使用CopyTo方法,所以这里是我的两个美分,一种易于解释和简单的解决方案。 CopyTo方法将数组复制到给定索引处的另一个数组。在这种情况下,将从索引1开始将myArray复制到newArr,以便newArr中的索引0可以容纳新值。

    " Push "来排列在线演示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var newValue = 1;
    var myArray = new int[5] { 2, 3, 4, 5, 6 };
    var newArr = new int[myArray.Length + 1];

    myArray.CopyTo(newArr, 1);
    newArr[0] = newValue;
       
    //debug
    for(var i = 0; i < newArr.Length; i++){
        Console.WriteLine(newArr[i].ToString());
    }

    //output
    1
    2
    3
    4
    5
    6

    我不明白您在使用for循环在做什么。您仅在遍历每个元素并将其分配给遇到的第一个元素。如果您要推送到列表,请使用上面的回答,指出没有推送到列表的事情。这确实使数据结构混杂在一起。 Javascript可能不是最好的例子,因为JavaScript列表实际上同时也是队列和堆栈。


    查看此文档页面:https://msdn.microsoft.com/zh-cn/library/ms132397(v = vs.110).aspx

    "添加"功能是"方法"下的第一个功能。