如何用一个值填充/实例化C#数组?

285
我知道在C#中,值类型的实例化数组会自动填充为该类型的默认值(例如,bool类型为false,int类型为0等)。
有没有办法用非默认的种子值自动填充数组?无论是在创建时还是之后的内置方法(类似于Java的Arrays.fill())?比如说,我想要一个默认为true的布尔数组,而不是false。有没有内置的方法可以实现这个,还是只能通过for循环遍历数组来实现?
 // Example pseudo-code:
 bool[] abValues = new[1000000];
 Array.Populate(abValues, true);

 // Currently how I'm handling this:
 bool[] abValues = new[1000000];
 for (int i = 0; i < 1000000; i++)
 {
     abValues[i] = true;
 }

需要遍历数组并将每个值“重置”为true似乎效率低下。有没有什么办法可以避免这种情况?也许通过翻转所有的值?

在写下这个问题并思考后,我猜测默认值只是C#在幕后处理这些对象的内存分配时的结果,所以我想这可能是不可能的。但我仍然想确切知道!


我通常将变量名从is_found更改为is_still_hiding。非常感谢提供的答案,我在测试用例中需要对int数组进行类似处理。(好问题) - ctrl-alt-delor
创建一个新的结构体,以实际使用您想要的默认值,也许? - arkon
26个回答

269
Enumerable.Repeat(true, 1000000).ToArray();

99
虽然这个方法可以运行,但并不是一个很好的解决方案,因为它非常缓慢;实际上比使用for循环迭代要慢4倍左右。 - patjbs
6
是的,这是真的。当我们考虑性能时,for循环更快。 - Rony
7
要查看一些真实的基准测试,请查看C# Initialize Array - theknut
4
Enumerable.ToArray 方法无法确定可枚举序列的大小,因此必须猜测数组大小。这意味着每当 ToArray 的缓冲区超过限制时,都会发生数组分配,最后还需要一个额外的修剪分配。还涉及可枚举对象的开销。 - Edward Brey
7
请注意,对于引用类型,这将使用同一个对象的所有引用来填充整个数组。如果这不是您想要的,并且您实际上希望为每个数组项生成不同的对象,请参见 https://dev59.com/sVcP5IYBdhLWcg3wDWRq#44937053。 - Alex Che
显示剩余7条评论

172

我不知道有没有现成的框架方法,但你可以编写一个快速的 helper 来完成它。

public static void Populate<T>(this T[] arr, T value ) {
  for ( int i = 0; i < arr.Length;i++ ) {
    arr[i] = value;
  }
}

6
如果你不需要拷贝的话,建议使用++i而不是i++。 - void.pointer
31
i++会先将i复制一份,然后将i加1,并返回原始值。++i则直接返回增加后的值。因此,在像我们正在讨论的大型循环中,++i更快,这可能非常重要。 - tenpn
84
这是编译器的一种优化方式,现在已经不再适用。我刚刚测试了一下以验证我的想法:如果i ++的返回值没有用于任何操作,则编译器会自动将其编译为++i。即使我使用返回值,性能差异也非常小,我需要制造一个极端情况来进行测量。即使如此,这只导致运行时间略有不同,最多只相差几个百分点。 - Edward Ned Harvey
15
我写了一个类似这样的扩展方法,但我让它返回原始数组,以便进行方法链式调用,例如:int[] arr = new int[16].Populate(-1); - Gutblender
6
void 改为 T[],然后你就可以执行 var a = new int[100].Polupate(1) - orad
显示剩余9条评论

99

创建一个包含一千个true值的新数组:

var items = Enumerable.Repeat<bool>(true, 1000).ToArray();  // Or ToList(), etc.

同样地,你可以生成整数序列:

var items = Enumerable.Range(0, 1000).ToArray();  // 0..999

16
还不错,但它仍然比for循环慢大约4倍。 - patjbs
1
理论上来说,在将来,Enumerable.Repeat会执行得更快,因为它将使用并行实现。 - Petar Petrov
2
@PetarPetrov 这种情况不会发生,因为缓存抖动的原因。我非常确定,由于CPU缓存的特性,对单个数组进行并行工作始终会更慢,因为计算机期望同步工作并适当地加载数据。 - TernaryTopiary
有意的悲观化并不等同于缺乏过早优化。 - Denis Gladkiy

77

您可以在 .NET Core 2.0+ 和 .NET Standard 2.1+ 中使用 Array.Fill


5
太好了!但要注意这是一种相对较新的方法。它在.NET Core 2.0+和.NET Standard 2.1中可用,但不包括任何.NET Framework版本。(它将出现在.NET 5.0中,它将把.NET Framework和.NET Core结合在一起)。 - Abel
4
好的,请提供需要翻译的内容。 - Chris Halcrow

27

对于大型数组或大小会变化的数组,您应该使用:

Enumerable.Repeat(true, 1000000).ToArray();

对于小数组,您可以在C# 3中使用集合初始化语法:

bool[] vals = new bool[]{ false, false, false, false, false, false, false };

使用集合初始化语法的好处是,您不必在每个插槽中使用相同的值,可以使用表达式或函数来初始化插槽。此外,我认为您可以避免将数组插槽初始化为默认值的成本。例如:

bool[] vals = new bool[]{ false, true, false, !(a ||b) && c, SomeBoolMethod() };

要初始化一个float[]数组:float[] AlzCalDefault = new float[] {(float) 0.5, 18, 500, 1, 0}; - Jim Lahman
就初始化数组而言,在任何版本的C#中都可以这样做:bool[] vals = { false, true, false, !(a || b) && c, SomeBoolMethod() }; - heijp06
1
“避免将数组插槽初始化为默认值的成本”是一个不错的想法,但是你和编译器都无法控制它——.NET分配器会分配已经清零的内存块。 - Ben Voigt

25

如果你的数组非常大,那么你应该使用 BitArray。它为每个 bool 使用 1 位而不是一个字节(例如在 bool 数组中),此外你可以使用位操作符将所有位设置为 true。或者只需初始化为 true。如果你只需要执行一次,则只会增加成本。

System.Collections.BitArray falses = new System.Collections.BitArray(100000, false);
System.Collections.BitArray trues = new System.Collections.BitArray(100000, true);

// Now both contain only true values.
falses.And(trues);

12

.NET Core 2.0及以上版本支持Array.Fill()方法。

以下是示例代码。

var arr = new int[10];
int defaultValue = 2;
Array.Fill(arr,defaultValue);

它还有一个用于填充索引范围的重载方法。更多细节可以在这里找到。


1
注意:相同的对象实例将用于所有的值,这意味着如果它有你想要稍后更新的字段(例如,arr[0].Value = 2),那么数组的所有值也会看到更新(例如,arr[1].Value == 2)。 - undefined

10

很遗憾,我认为没有直接的方法,但是我认为您可以为数组类编写扩展方法来实现此操作。

class Program
{
    static void Main(string[] args)
    {
        int[] arr = new int[1000];
        arr.Init(10);
        Array.ForEach(arr, Console.WriteLine);
    }
}

public static class ArrayExtensions
{
    public static void Init<T>(this T[] array, T defaultVaue)
    {
        if (array == null)
            return;
        for (int i = 0; i < array.Length; i++)
        {
            array[i] = defaultVaue;
        }
    }
}

我越深入探索,就越喜欢这个扩展想法。有时候最简单和直接的方案确实是最好的! - patjbs

9

经过更多的搜索和阅读,我找到了这个:

bool[] bPrimes = new bool[1000000];
bPrimes = Array.ConvertAll<bool, bool>(bPrimes, b=> b=true);

这可能更接近我所需的内容。但我不确定它是否比通过for循环迭代原始数组并仅更改值更好。实际上,经过快速测试,它似乎慢了约5倍。因此,这并不是一个好的解决方案!


4
这与你正在尝试做的类似,只不过它会针对数组中的每个元素进行一次函数调用。虽然在语法上看起来更加优美,但实际上需要进行更多的工作… - Nader Shirazie
是的,看起来一个简单的for循环就可以胜任这项工作,几乎和其他任何方法一样好。 - patjbs
1
它创建一个新的数组(不改变原始实例)。 - Jeppe Stig Nielsen

9
下面的代码结合了简单迭代和Array.Copy进行大规模复制。
    public static void Populate<T>( T[] array, int startIndex, int count, T value ) {
        if ( array == null ) {
            throw new ArgumentNullException( "array" );
        }
        if ( (uint)startIndex >= array.Length ) {
            throw new ArgumentOutOfRangeException( "startIndex", "" );
        }
        if ( count < 0 || ( (uint)( startIndex + count ) > array.Length ) ) {
            throw new ArgumentOutOfRangeException( "count", "" );
        }
        const int Gap = 16;
        int i = startIndex;

        if ( count <= Gap * 2 ) {
            while ( count > 0 ) {
                array[ i ] = value;
                count--;
                i++;
            }
            return;
        }
        int aval = Gap;
        count -= Gap;

        do {
            array[ i ] = value;
            i++;
            --aval;
        } while ( aval > 0 );

        aval = Gap;
        while ( true ) {
            Array.Copy( array, startIndex, array, i, aval );
            i += aval;
            count -= aval;
            aval *= 2;
            if ( count <= aval ) {
                Array.Copy( array, startIndex, array, i, count );
                break;
            }
        }
    }

使用int[]数组时,不同数组长度的基准测试结果如下:
         2 Iterate:     1981 Populate:     2845
         4 Iterate:     2678 Populate:     3915
         8 Iterate:     4026 Populate:     6592
        16 Iterate:     6825 Populate:    10269
        32 Iterate:    16766 Populate:    18786
        64 Iterate:    27120 Populate:    35187
       128 Iterate:    49769 Populate:    53133
       256 Iterate:   100099 Populate:    71709
       512 Iterate:   184722 Populate:   107933
      1024 Iterate:   363727 Populate:   126389
      2048 Iterate:   710963 Populate:   220152
      4096 Iterate:  1419732 Populate:   291860
      8192 Iterate:  2854372 Populate:   685834
     16384 Iterate:  5703108 Populate:  1444185
     32768 Iterate: 11396999 Populate:  3210109

第一列是数组大小,其后是使用简单迭代(@JaredPared实现)进行复制的时间。此方法的时间在其后。

这些是使用一个包含四个整数的结构体数组的基准测试结果。

         2 Iterate:     2473 Populate:     4589
         4 Iterate:     3966 Populate:     6081
         8 Iterate:     7326 Populate:     9050
        16 Iterate:    14606 Populate:    16114
        32 Iterate:    29170 Populate:    31473
        64 Iterate:    57117 Populate:    52079
       128 Iterate:   112927 Populate:    75503
       256 Iterate:   226767 Populate:   133276
       512 Iterate:   447424 Populate:   165912
      1024 Iterate:   890158 Populate:   367087
      2048 Iterate:  1786918 Populate:   492909
      4096 Iterate:  3570919 Populate:  1623861
      8192 Iterate:  7136554 Populate:  2857678
     16384 Iterate: 14258354 Populate:  6437759
     32768 Iterate: 28351852 Populate: 12843259

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接