"push" 到 C# 数组的最佳方法

Best way to "push" into C# array

大家好

所以我知道这是一个讨论相当广泛的问题,但我似乎找不到明确的答案。我知道您可以使用列表来完成我要求的操作,但这并不能解决我的问题。

我教 class 初学者 C#。我有 PHP 和 Java 脚本背景,因此倾向于从这些语言的角度来思考。

我一直在寻找向我的学生展示如何向 fly.We 上的数组添加元素的最佳方法不能使用列表,因为这些不是我目前教授的教学大纲的一部分(尽管他们确实会在本学期晚些时候进来)。

因此,我正在寻找最简单的方法来执行 Java array.push(newValue) 类型的场景,新手编码人员可以理解这种场景,而不会教给他们不好的做法。

到目前为止,我是这样处理这个问题的:

for (int i = 0; i < myArray.Length; i++)
{
   if(myArray[i] == null)
   {
       myArray[i] = newValue;
       break;
   }
}

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

编辑 问题的症结在于需要将元素添加到数组中的第一个空槽中,谎言 Java push 函数就可以了。

如有任何建议,我们将不胜感激。

根据评论 "That is not pushing to an array. It is merely assigning to it"

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

Array[index]= value;

只有当你不想使用时才可以赋值List

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

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.

我提供的代码有效地附加了一个元素。如果目的是设置第一个空元素,那么你可以这样做:

int index = Array.IndexOf(myArray, null);

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

编辑:

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

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;
    }
}

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

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");
}

这作为分配给数组是可以接受的。但是如果你要求推动,我很确定它不可能在阵列中。相反,它可以通过使用 Stack、Queue 或任何其他数据结构来实现。 真正的数组没有这样的功能。但是派生类比如ArrayList都有它。

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

Add 函数是 Methods 下的第一个函数。

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

有几种方法可以做到这一点。

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

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

现在不推荐这样做,因为您转换为列表并再次返回数组。如果你不想使用列表,你可以使用第二种方式直接赋值到数组:

int[] terms = new int[400];
for (int runs = 0; runs < 400; runs++)
{
    terms[runs] = value;
}

这是直接的方法,如果您不想纠结于列表和转换, 这是给你推荐的。

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

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 略有不同。由于严格的检查,you 定义了数组的大小并且 you 应该知道数组的一切,例如它的边界,其中最后一件物品已放入,哪些物品未使用。如果你想调整它的大小,你应该将数组的所有元素复制到一个新的更大的元素中。

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

但是,如果您想让运行时维护此信息并完全抽象掉数组,但仍使用下面的数组,那么 C# 提供了一个名为 class 的 ArrayList,它提供这种抽象。

ArrayList 抽象了一个松散类型的 Object 数组。查看来源 here.

它处理所有问题,例如调整数组大小、维护可用的最后一个索引等。但是,它从您那里抽象/封装了这些信息。您只使用 ArrayList.

要将任何类型的项目推送到基础数组末尾的基础数组中,请调用 ArrayList 上的 Add 方法,如下所示:

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

arrayList.Add("Foo");

编辑:关于类型限制的注释

与所有编程语言和运行时一样,C# 将堆对象与不会进入堆且只会留在函数的参数堆栈中的对象进行分类。 C# 通过名称 Value types vs. Reference types 注意到这种区别。所有值在堆栈上的东西都称为值类型,而那些将在堆上的东西称为引用类型。这与 JavaScript 对 objectsliterals.

的区分大致相似

您可以将 任何东西 放入 C# 中的 ArrayList,无论该东西是值类型还是引用类型。这使得它在 无类型 方面最接近 JavaScript 数组,尽管这三个都不是 JavaScript 数组、JavaScript 语言和C# ArrayList——实际上是无类型的。

因此,您可以将数字文字、字符串文字、您编写的 class 的对象、布尔值、浮点数、双精度数、结构体,几乎任何您想要的东西放入ArrayList.

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

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

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

// Valid javascript
var s = 4.toString();

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

就像 JavaScript 在调用 toString 方法时将数字文字 4 装箱一样,C# 将所有非对象的东西放入 Object 类型时将它们放入ArrayList.

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>。因此,您不能将 anything 放入 List<T>;只有 T 类型。

但是,根据您的问题文本,我假设您已经知道 C# 具有用于强类型项的通用结构。你的问题是要有一个像 JavaScript 这样的数据结构来展示 无类型 和弹性的语义,比如 JavaScript Array 对象。在这种情况下,ArrayList 最接近。

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

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

我不明白你在用 for 循环做什么。您只是遍历每个元素并分配给您遇到的第一个元素。如果您尝试推送到列表,请使用上面的答案,该答案指出没有推送到列表这样的事情。这真的是混淆了数据结构。 Javascript 可能不是最好的例子,因为 javascript 列表实际上同时也是一个队列和一个堆栈。

你的问题有点离题。特别是,你说 "that the element needs to be added into the first empty slot in an array, lie (sic) a Java push function would do."

  1. Java 的数组没有推送操作 - Java脚本有。 Java 和 JavaScript 是两种非常 不同的语言
  2. Java脚本的推送功能与您描述的不一样。当您 "push" 将一个值放入 Java 脚本数组时,该数组将扩展一个元素,并为该新元素分配推入的值,请参阅:Mozilla's Array.prototype.push function docs

动词 "Push" 不是我所知道的任何语言中与数组一起使用的东西,除了 JavaScript。我怀疑它只存在于 JavaScript 中,因为它可能存在(因为 JavaScript 是一种完全动态的语言)。我很确定它不是故意设计的。

C# 中的 Java 脚本样式推送操作可以用这种效率较低的方式编写:

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# 中,与在大多数传统过程语言中一样,数组是单一类型元素的集合,包含在固定长度的连续内存块中。当你分配一个数组时,每个数组元素的 space 都会被分配(并且,在 C# 中,这些元素被初始化为类型的默认值,null 用于引用类型) .

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

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

数组是可变的(数组中的值可以写入),但它们的长度是固定的——它们不能被延长或缩短。它们是有序的,不能重新排列(除非手动调整值)。

C# 数组是协变的。如果你去问 C# 语言的设计者,这将是他们最后悔的特性。这是可以破坏 C# 类型安全的少数方法之一。考虑这段代码(假设 Cat 和 Dog class 继承自 Animal):

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!

"feature" 是 .NET Framework 初始版本中缺少通用类型和显式 covariance/contravariance 以及复制 Java [=78] 的冲动的结果=].

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

一般来说,有经验的 C# 程序员不会在他的应用程序中使用很多数组,更愿意使用功能更强大的集合,例如 List<T>Dictionary<TKey, TValue>HashSet<T> 和朋友。特别是,该程序员将倾向于使用 IEnumerable<T> 所有集合都实现的接口来传递集合。使用 IEnumerable<T> 作为参数和 return 类型(在可能和合乎逻辑的情况下)的最大优势是通过 IEnumerable<T> 引用访问的集合是不可变的。这有点像在 C++ 中正确使用 const

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

最后,LINQ(语言集成查询)为集合引入了很多功能(通过添加 Extension MethodsIEnumerable<T>)。确保您的学生的代码顶部没有 using System.Linq; 语句 - 将 LINQ 混入初学者的 class 数组会使他或她感到困惑。

BTW:你教的是什么class?什么水平?

我建议使用 CopyTo method,所以这是我对一个易于解释且简单的解决方案的两分钱。 CopyTo 方法将数组复制到给定索引处的另一个数组。在这种情况下,myArray 从索引 1 开始复制到 newArr,因此 newArr 中的索引 0 可以保存新值。

"Push" to array online demo

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

这是我的解决方案

public void ArrayPush<T>(ref T[] table, object value)
{
    Array.Resize(ref table, table.Length + 1); // Resizing the array for the cloned length (+-) (+1)
    table.SetValue(value, table.Length - 1); // Setting the value for the new element
}

如何使用?就这么简单。
Array Push Example

string[] table = { "apple", "orange" };
ArrayPush(ref table, "banana");

Array.ForEach(table, (element) => Console.WriteLine(element));
// "apple"
// "orange"
// "banana"

非常简单实用。
这有点棘手,但它有效