C#中的数组切片

Mat*_*ley 214 c# arrays

你怎么做呢?给定一个字节数组:

byte[] foo = new byte[4096];
Run Code Online (Sandbox Code Playgroud)

如何将数组的前x个字节作为单独的数组?(具体来说,我需要它作为一个IEnumerable<byte>)

这是为了与Sockets合作.我认为最简单的方法是数组切片,类似于Perls语法:

@bar = @foo[0..40];
Run Code Online (Sandbox Code Playgroud)

这会将前41个元素返回到@bar数组中.C#中有些东西我只是缺少,还是还有其他一些我应该做的事情?

LINQ对我来说是一个选项(.NET 3.5),如果这有帮助的话.

Mik*_*ott 202

你可以用ArraySegment<T>.它的重量非常轻,因为它不会复制数组:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );
Run Code Online (Sandbox Code Playgroud)

  • ArraySegment是从.Net 4.5开始的IList和IEnumerable.对于旧版本用户来说太糟糕了.. (37认同)
  • 有谁知道为什么它不是IEnumerable?我不.看起来应该是这样. (22认同)
  • @Zyo我的意思是ArraySegment <T>从.Net 4.5开始实现IEnumerable <T>,而不是IEnumerable <T>本身是新的. (5认同)
  • 不幸的是,它不是IEnumerable. (4认同)
  • @RonKlein,我知道它是一个包装器,它不会复制。你剩下的解释让我失望了。 (3认同)

peS*_*HIr 186

数组是可枚举的,所以你foo已经是一个IEnumerable<byte>本身.只需使用LINQ序列方法Take()就可以得到你想要的东西(不要忘记包含Linq命名空间 using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);
Run Code Online (Sandbox Code Playgroud)

如果你真的需要任何IEnumerable<byte>值的数组,你可以使用这个ToArray()方法.这似乎不是这种情况.

  • 使用Array.Copy的注意事项比使用LINQ的Take或Skip方法快得多. (11认同)
  • 如果我们要复制到另一个数组,只需使用Array.Copy静态方法.但是我认为其他答案已正确解释了意图,另外一个数组不需要只有前31个字节的IEnumberable <byte>. (5认同)
  • @Abel这实际上是非常不正确的.多维数组*是*可枚举的,但它们枚举如下:`[2,3] => [1,1],[1,2],[1,3],[2,1],[2,2] ,[2,3]`.锯齿状数组也是可枚举的,但它们不是在枚举时返回值,而是返回它们的内部数组.像这样:`type [] [] jaggedArray; foreach(在jaggedArray中键入[] innerArray){}` (4认同)
  • @Aidiakapi"非常直接"?).但你部分正确,我应该写"多字节数组不实现`IEnumerable <T>`",然后我的陈述会更清楚.另见:http://stackoverflow.com/questions/721882/multidimensional-arrays-do-not-implement-ienumerablet-or-do-they (3认同)
  • 请注意,只有单维和锯齿状数组是可枚举的,而多维数组则不是. (2认同)

Arj*_*nbu 130

您可以使用数组CopyTo()方法.

或者使用LINQ,您可以使用Skip()Take()...

byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);
Run Code Online (Sandbox Code Playgroud)

  • 这种方法比Array.Copy慢至少50倍.这在许多情况下不是问题,但在循环中进行数组切片时,性能下降非常明显. (10认同)
  • 我还不熟悉LINQ,也许这是我真正应该做的进一步证据. (3认同)
  • 我打了一个电话,所以演出对我来说不是问题.这非常适合阅读......谢谢. (3认同)
  • 谢谢你的"Skip()".只是`Take()`不会让你获得任意切片.此外,我一直在寻找一个LINQ解决方案(切片IEnumerable,但我知道有关数组的结果会更容易找到). (2认同)

WOP*_*OPR 52

static byte[] SliceMe(byte[] source, int length)
{
    byte[] destfoo = new byte[length];
    Array.Copy(source, 0, destfoo, 0, length);
    return destfoo;
}
Run Code Online (Sandbox Code Playgroud)

//

var myslice = SliceMe(sourcearray,41);
Run Code Online (Sandbox Code Playgroud)

  • 我认为Buffer.BlockCopy()效率更高,并获得相同的结果. (9认同)

Rem*_*_rm 19

从C#8.0 / .Net Core 3.0开始

将支持数组切片,IndexRange添加新的类型。

范围结构文档
索引结构文档

Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

var slice = a[i1..i2]; // { 3, 4, 5 }
Run Code Online (Sandbox Code Playgroud)

上面的代码示例摘自C#8.0 博客

请注意,^前缀表示从数组末尾开始计数。如docs示例所示

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0
Run Code Online (Sandbox Code Playgroud)

Range并且Index还可以在切片数组之外工作,例如使用循环

Range range = 1..4; 
foreach (var name in names[range])
Run Code Online (Sandbox Code Playgroud)

将遍历条目1至4


请注意,在编写此答案时,C#8.0尚未正式发布

  • 可笑的是,没有任何文档告诉我切片后得到什么类型。是另一个数组吗?或者它是一个ArraySegment?或者是什么?... (3认同)
  • 知道这是否会创建数组的副本吗? (2认同)
  • 似乎是副本:https://www.codejourney.net/2019/02/csharp-8-slicing-indexes-ranges/ (2认同)

Ken*_*ith 16

我在这里没有提到的另一种可能性:Buffer.BlockCopy()比Array.Copy()略快,它还具有能够从原始数组中即时转换的好处(比如说,简短) [])到一个字节数组,当你需要通过套接字传输数字数组时,它可以很方便.

  • @jocull - 他们实际上并没有采用相同的参数.Array.Copy()在元素中获取其长度和位置参数.Buffer.BlockCopy()以字节为单位获取其长度和位置参数.换句话说,如果你想复制一个10元素的整数数组,你可以使用`Array.Copy(array1,0,array2,0,10)`,但是`Buffer.BlockCopy(array1,0,array2,0) ,10*sizeof(int))`. (6认同)
  • `Buffer.BlockCopy`产生的结果与`Array.Copy()`不同,即使它们接受相同的参数 - 有很多空元素.为什么? (2认同)

Pat*_*man 16

C#7.2中,您可以使用Span<T>.新System.Memory系统的好处是它不需要复制数据.

你需要的方法是Slice:

Span<byte> slice = foo.Slice(0, 40);
Run Code Online (Sandbox Code Playgroud)

很多方法现在支持SpanIReadOnlySpan,所以这将是非常简单的使用这个新的类型.

请注意,在编写本文时,Span<T>尚未在最新版本的.NET(4.7.1)中定义类型,因此要使用它,您需要从NuGet 安装System.Memory包.


Vla*_*vic 14

这是一个简单的扩展方法,它将切片作为新数组返回:

public static T[] Slice<T>(this T[] arr, uint indexFrom, uint indexTo) {
    if (indexFrom > indexTo) {
        throw new ArgumentOutOfRangeException("indexFrom is bigger than indexTo!");
    }

    uint length = indexTo - indexFrom;
    T[] result = new T[length];
    Array.Copy(arr, indexFrom, result, 0, length);

    return result;
}
Run Code Online (Sandbox Code Playgroud)

然后你可以用它作为:

byte[] slice = foo.Slice(0, 40);
Run Code Online (Sandbox Code Playgroud)


Mar*_*ell 13

如果你想IEnumerable<byte>,那就是

IEnumerable<byte> data = foo.Take(x);
Run Code Online (Sandbox Code Playgroud)


Maj*_*jor 9

C# 8 现在(自 2019 年起)支持Ranges,这使您可以更轻松地实现 Slice(类似于 JS 语法):

var array = new int[] { 1, 2, 3, 4, 5 };
var slice1 = array[2..^3];    // array[new Range(2, new Index(3, fromEnd: true))]
var slice2 = array[..^3];     // array[Range.EndAt(new Index(3, fromEnd: true))]
var slice3 = array[2..];      // array[Range.StartAt(2)]
var slice4 = array[..];       // array[Range.All]
Run Code Online (Sandbox Code Playgroud)

您可以使用范围代替众所周知的 LINQ 函数:Skip()Take()Count()

  • 变变变变变。不知道返回的是什么类型 (3认同)

Dim*_*ris 8

如果您不想添加LINQ或其他扩展,请执行以下操作:

float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
Run Code Online (Sandbox Code Playgroud)

  • `System.Collections.Generic.List` (2认同)

Rau*_*otz 7

您可以在原始数组(IList)周围使用包装器,就像在这个(未经测试的)代码段中一样.

public class SubList<T> : IList<T>
{
    #region Fields

private readonly int startIndex;
private readonly int endIndex;
private readonly int count;
private readonly IList<T> source;

#endregion

public SubList(IList<T> source, int startIndex, int count)
{
    this.source = source;
    this.startIndex = startIndex;
    this.count = count;
    this.endIndex = this.startIndex + this.count - 1;
}

#region IList<T> Members

public int IndexOf(T item)
{
    if (item != null)
    {
        for (int i = this.startIndex; i <= this.endIndex; i++)
        {
            if (item.Equals(this.source[i]))
                return i;
        }
    }
    else
    {
        for (int i = this.startIndex; i <= this.endIndex; i++)
        {
            if (this.source[i] == null)
                return i;
        }
    }
    return -1;
}

public void Insert(int index, T item)
{
    throw new NotSupportedException();
}

public void RemoveAt(int index)
{
    throw new NotSupportedException();
}

public T this[int index]
{
    get
    {
        if (index >= 0 && index < this.count)
            return this.source[index + this.startIndex];
        else
            throw new IndexOutOfRangeException("index");
    }
    set
    {
        if (index >= 0 && index < this.count)
            this.source[index + this.startIndex] = value;
        else
            throw new IndexOutOfRangeException("index");
    }
}

#endregion

#region ICollection<T> Members

public void Add(T item)
{
    throw new NotSupportedException();
}

public void Clear()
{
    throw new NotSupportedException();
}

public bool Contains(T item)
{
    return this.IndexOf(item) >= 0;
}

public void CopyTo(T[] array, int arrayIndex)
{
    for (int i=0; i<this.count; i++)
    {
        array[arrayIndex + i] = this.source[i + this.startIndex];
    }
}

public int Count
{
    get { return this.count; }
}

public bool IsReadOnly
{
    get { return true; }
}

public bool Remove(T item)
{
    throw new NotSupportedException();
}

#endregion

#region IEnumerable<T> Members

public IEnumerator<T> GetEnumerator()
{
    for (int i = this.startIndex; i < this.endIndex; i++)
    {
        yield return this.source[i];
    }
}

#endregion

#region IEnumerable Members

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

#endregion
Run Code Online (Sandbox Code Playgroud)

}

  • 我建议使用EqualityComparer.Default for IndexOf - 这样你就不需要任何特殊的套管了. (4认同)

小智 6

byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();
Run Code Online (Sandbox Code Playgroud)


Sim*_*les 6

对于字节数组,System.Buffer.BlockCopy将为您提供最佳性能。


aku*_*aku 5

您可以使用Take扩展方法

var array = new byte[] {1, 2, 3, 4};
var firstTwoItems = array.Take(2);
Run Code Online (Sandbox Code Playgroud)