我有10个元素的数组X。我想创建一个新数组,其中包含X中所有从索引3开始到索引7结束的元素。当然,我可以轻松编写一个循环来为我做一个循环,但是我想保持代码尽可能整洁。 C#中有什么方法可以帮我吗?

类似(伪代码)的东西:

Array NewArray = oldArray.createNewArrayFromRange(int BeginIndex , int EndIndex)


Array.Copy不适合我的需求我需要新阵列中的项目才能克隆。 Array.copy只是C风格的memcpy的等效物,这不是我想要的。

评论

重复:stackoverflow.com/questions/406485/array-slices-in-c

@Kirtan-“ dup”特别想要IEnumerable -这是不同的,并且具有不同的最佳解决方案; IMO

那么,声明新数组并调用.Copy()所需的两行不是“干净的代码”吗?

@Ed Swangren-不需要,如果您需要在链接表达式的中间进行操作,则不行; -p

ShaggyUk的答案可能是正确的答案:stackoverflow.com/questions/943635/…

#1 楼

您可以将其添加为扩展方法:

public static T[] SubArray<T>(this T[] data, int index, int length)
{
    T[] result = new T[length];
    Array.Copy(data, index, result, 0, length);
    return result;
}
static void Main()
{
    int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int[] sub = data.SubArray(3, 4); // contains {3,4,5,6}
}



更新重新克隆(在原始问题中并不明显)。如果您真的想要深入的克隆,就像这样:

public static T[] SubArrayDeepClone<T>(this T[] data, int index, int length)
{
    T[] arrCopy = new T[length];
    Array.Copy(data, index, arrCopy, 0, length);
    using (MemoryStream ms = new MemoryStream())
    {
        var bf = new BinaryFormatter();
        bf.Serialize(ms, arrCopy);
        ms.Position = 0;
        return (T[])bf.Deserialize(ms);
    }
}


但这确实要求对象是可序列化的([Serializable]ISerializable)。您可以根据需要轻松替换任何其他序列化器-XmlSerializerDataContractSerializer,protobuf-net等。特别是在大多数情况下,很难相信ICloneable

评论


(显然,使用结束索引而不是长度是一个简单的更改;我发布了“原样”,因为这是更“典型”的用法)

– Marc Gravell♦
09年3月3日在8:39

然后...强硬它不会那样做。...您可能需要使用序列化来实现类似的功能

– Marc Gravell♦
09年3月3日在19:42

请参阅我的答案以获取一些替代方法以及指向多个实现的链接。对子数组执行操作的部分确实很琐碎,您真正想要的是克隆位,这是一个复杂且有点开放的问题,完全取决于您对“正确”行为的期望。

– ShuggyCoUk
09年7月8日在16:17

很好特别要指出的是,ICloneable是不可靠的,因为它曾经存在过。

– Marcus Griep
09年7月14日在2:02

感谢您强调C#中的深度克隆问题。真的很遗憾,因为深度复制是一项基本操作。

– Dimitri C.
2011-2-22在8:38

#2 楼

创建新数组后,可以使用Array.Copy(...)将其复制到新数组中,但是我认为没有一种方法可以创建新数组并复制一系列元素。

使用.NET 3.5,您可以使用LINQ:

var newArray = array.Skip(3).Take(5).ToArray();


,但是效率会稍差。

对此类似问题的答案参见针对特定情况的选项。

评论


+1我也喜欢这种变化。乔恩(Jon),您能解释一下为什么效率低吗?

–伊恩·洛克(Ian Roke)
2009年6月3日,9:28

@乔恩:为了解决这个问题,不是“ Take(5)”吗? @Ian:Array.Copy方法不涉及枚举器,最有可能是直接的内存复制...

– Marc Gravell♦
09年6月3日,9:35

@马克:是的。撇去问题太多了:)

–乔恩·斯基特(Jon Skeet)
09年6月3日,9:57

@Ian:LINQ方法引入了两个级别的间接(迭代器),必须显式跳过项目,并且不知道最终数组将有多大。考虑使用200万个元素的数组的后半部分:一种简单的“创建目标数组,复制”方法将只复制所需的块,而不会触及其他元素。 LINQ方法将遍历数组直到到达起点,然后开始获取值,建立缓冲区(增加缓冲区大小并定期复制)。效率低得多。

–乔恩·斯基特(Jon Skeet)
2009年6月3日,9:59

如果5是EndIndexm,则正确的问题是array.Skip(3).Take(5-3 + 1).ToArray();即。 array.Skip(StartIndex).Take(EndIndex-StartIndex + 1).ToArray();

– Klaus78
2014年12月3日下午16:01

#3 楼

您考虑使用ArraySegment吗?

http://msdn.microsoft.com/en-us/library/1hsbd92d.aspx

评论


它可能会满足您的要求,但是它不支持默认的数组语法,也不支持IEnumerable,因此它不是特别干净。

– Alex Black
2009年10月8日15:57

这需要更多的支持。在我自己的实验中,ArraySegment的复制也略快一些(毕竟我将数组用于速度至关重要的东西)。

– nawfal
2012年11月15日下午13:17

@AlexBlack从.NET 4.5开始,它实现了IEnumerable 和其他各种有用的接口。

– p.s.w.g
2013年12月26日下午16:21

您将如何使用ArraySegment回答原始问题?

–克雷格·麦昆(Craig McQueen)
15年3月18日在0:43

@CraigMcQueen-尝试以下单行方法:IList newArray =(IList )new ArraySegment (oldArray,beginIndex,endIndex);

–skia.heliou
15年7月21日在17:30



#4 楼

我看到您想要克隆,而不仅仅是复制引用。
在这种情况下,您可以使用.Select将数组成员投影到其克隆中。
例如,如果您的元素实现了IClonable,则可以执行以下操作:

var newArray = array.Skip(3).Take(5).Select(eachElement => eachElement.Clone()).ToArray();


注意:此解决方案需要.NET Framework 3.5。

评论


这样更优雅。

–smwikipedia
2014年3月24日5:56

这正是我想要的。这适用于任何IEnumerable。我可以以最小的麻烦获得IEnumerable,IList,IArray等...,如果需要的话,可以内联。如果不需要深层副本,只需删除“选择”即可。放下“跳过”或“取走”可让我控制范围。或者,我可以将其与SkipWhile和/或TakeWhile混合使用。

–迈克
17年9月14日在0:49

#5 楼

以下代码可以一行完成:

// Source array
string[] Source = new string[] { "A", "B", "C", "D" };
// Extracting a slice into another array
string[] Slice = new List<string>(Source).GetRange(2, 2).ToArray();


评论


单行,无需添加Linq。这是我的首选方式。

– Dimitris
15年6月12日在20:11

仍然它没有克隆源...但是无论如何这是一个好方法

– I.G. Pascual
16 Mar 23 '16 at 12:56

它应该克隆源,因为ToArray:(1)创建一个新数组,(2)执行Array.Copy。最后,Source和Slice是两个单独的对象。该方法是正确的,但是,我更喜欢Array.Copy:referencesource.microsoft.com/#mscorlib/system/collections/…

–克劳斯
16/09/4在20:23



#6 楼

在C#8中,他们引入了新的RangeIndex类型,可以像这样使用:
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
var slice = a[i1..i2]; // { 3, 4, 5 }

参考文献:

https://docs.microsoft.com / en-us / dotnet / core / whats-new / dotnet-core-3-0#ranges-and-indices
https://devblogs.microsoft.com/dotnet/building-c-8-0/


#7 楼

string[] arr = { "Parrot" , "Snake" ,"Rabbit" , "Dog" , "cat" };

arr = arr.ToList().GetRange(0, arr.Length -1).ToArray();


#8 楼

基于Marc的答案,但添加了所需的克隆行为
public static T[] CloneSubArray<T>(this T[] data, int index, int length)
    where T : ICloneable
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    { 
        var original = data[index + i];
        if (original != null)
            result[i] = (T)original.Clone();            
    return result;
}

如果实现ICloneable太难了,那么可以使用HåvardStranden的Copyable库来执行所需的繁重工作。 br />请注意,OX.Copyable实现可与以下任何一种一起使用:

要使自动副本起作用,例如,以下语句之一必须保留:

其类型必须具有无参数的构造函数,或者
它必须是可复制的,或者
它必须具有为其类型注册的IInstanceProvider。


因此这应该涵盖几乎您遇到的任何情况。如果要克隆子图包含db连接或文件/流句柄之类的对象的对象,显然会遇到问题,但对于任何通用的深层复制都是如此。
如果要使用其他深层复制方法,请代替本文列出了其他几个,所以我建议不要尝试编写自己的。

评论


第一个可能是所需的解决方案,因为他正在请求克隆。请注意,使用Copy方法时,如果该方法本身已经执行了此操作,则可能甚至不必检查null,因为它是扩展方法。值得一试。

– Dykam
09年7月11日在8:02

是的,我注意到了空检查,但不想混淆OP,以防他不阅读源代码。

– ShuggyCoUk
09年7月11日在11:22

只是一个旁注:GitHub上的最新版本Copyable不需要对象具有无参数构造函数。 :)参见github.com/havard/copyable

–Håvard S
2010-2-2在8:28

#9 楼

您可以相当轻松地完成此操作;



    object[] foo = new object[10];
    object[] bar = new object[7];   
    Array.Copy(foo, 3, bar, 0, 7);  


评论


不,bar仍为空。 Array.Copy不会神奇地创建新的数组,尤其是因为bar不会随ref或out传递。

–Zr40
09年7月8日在16:16

哦,嘿,您的权利,我匆匆忙忙地做了,但是,嘿,也许当您的写作批评成为现实时,建设性的批评对每个人都非常有用。因此在该array.copy之前,您需要执行“ bar = new object [7];”

–RandomNickName42
09年7月11日在7:58

#10 楼

我认为您正在寻找的代码是:

Array.Copy(oldArray, 0, newArray, BeginIndex, EndIndex - BeginIndex)

评论


我想我已经在这里交了一些好朋友了。。。。。。。哈哈!无论如何,美好时光美好时光。

–RandomNickName42
09年7月14日在2:41

#11 楼

作为复制数据的替代方法,您可以制作一个包装程序,使您可以访问原始数组的一部分,就好像它是该数组的一部分的副本一样。优点是您不会在内存中获得数据的另一个副本,缺点是访问数据时会产生一些开销。

public class SubArray<T> : IEnumerable<T> {

   private T[] _original;
   private int _start;

   public SubArray(T[] original, int start, int len) {
      _original = original;
      _start = start;
      Length = len;
   }

   public T this[int index] {
      get {
         if (index < 0 || index >= Length) throw new IndexOutOfRangeException();
         return _original[_start + index];
      }
   }

   public int Length { get; private set; }

   public IEnumerator<T> GetEnumerator() {
      for (int i = 0; i < Length; i++) {
        yield return _original[_start + i];
      }
   }

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

}


用法:

int[] original = { 1, 2, 3, 4, 5 };
SubArray<int> copy = new SubArray<int>(original, 2, 2);

Console.WriteLine(copy.Length); // shows: 2
Console.WriteLine(copy[0]); // shows: 3
foreach (int i in copy) Console.WriteLine(i); // shows 3 and 4


评论


@罗伯特:不,不是。尝试改用ArraySegment,您会看到既无法按索引访问项目,也无法遍历项目。

–古法
09年10月8日在15:42

#12 楼

Array.ConstrainedCopy将起作用。

public static void ConstrainedCopy (
    Array sourceArray,
    int sourceIndex,
    Array destinationArray,
    int destinationIndex,
    int length
)


评论


那只是复制数据;它不会创建新的数组等;如果数组是新数组,则可以使用效率更高的Array.Copy(不需要其他检查/回滚)。

– Marc Gravell♦
09年3月3日在8:38

没错,但是创建一个新数组只是一行代码,不需要任何新方法。我同意Array.Copy也可以使用。

–crauscher
09年3月3日在8:48

#13 楼

没有任何一种方法可以满足您的需求。您将需要为数组中的类提供一个克隆方法。然后,如果可以选择LINQ:

Foo[] newArray = oldArray.Skip(3).Take(5).Select(item => item.Clone()).ToArray();

class Foo
{
    public Foo Clone()
    {
        return (Foo)MemberwiseClone();
    }
}


#14 楼

如何使用Array.ConstrainedCopy:下面是我的原始帖子。它不起作用

您可以使用Array.CopyTo:

int[] ArrayOne = new int[8] {1,2,3,4,5,6,7,8};
int[] ArrayTwo = new int[5];
Array.ConstrainedCopy(ArrayOne, 3, ArrayTwo, 0, 7-3);


#15 楼

怎么样:

public T[] CloneCopy(T[] array, int startIndex, int endIndex) where T : ICloneable
{
    T[] retArray = new T[endIndex - startIndex];
    for (int i = startIndex; i < endIndex; i++)
    {
        array[i - startIndex] = array[i].Clone();
    }
    return retArray;

}


然后您需要在所有需要使用此类的类上实现ICloneable接口,但这应该可以实现。

#16 楼

我不确定它到底有多深,但是:

#17 楼

在C#8.0中,您现在可以执行许多更出色的工作,包括像Python中那样的反向索引和范围,例如:
int[] list = {1, 2, 3, 4, 5, 6};
var list2 = list[2..5].Clone() as int[]; // 3, 4, 5
var list3 = list[..5].Clone() as int[];  // 1, 2, 3, 4, 5
var list4 = list[^4..^0].Clone() as int[];  // reverse index


#18 楼

就克隆而言,我不认为序列化会调用您的构造函数。如果您在ctor中执行有趣的操作,这可能会破坏类不变式。

似乎更安全的选择是调用副本构造函数的虚拟克隆方法。

protected MyDerivedClass(MyDerivedClass myClass) 
{
  ...
}

public override MyBaseClass Clone()
{
  return new MyDerivedClass(this);
}


评论


序列化是否调用构造函数取决于特定的序列化器。有些会,有些不会。但是那些通常不提供回调支持的功能使您可以进行所需的修复。

– Marc Gravell♦
09年7月14日在4:12

这突出了序列化的另一个障碍:您必须提供默认的构造函数。

–汉斯·马尔赫贝(Hans Malherbe)
09年7月14日在7:32

#19 楼

不能以通用方式克隆数组中的元素。您要深度克隆还是所有成员的简单副本?

让我们采取“尽力而为”的方法:使用ICloneable接口或二进制序列化克隆对象:

public static class ArrayExtensions
{
  public static T[] SubArray<T>(this T[] array, int index, int length)
  {
    T[] result = new T[length];

    for (int i=index;i<length+index && i<array.Length;i++)
    {
       if (array[i] is ICloneable)
          result[i-index] = (T) ((ICloneable)array[i]).Clone();
       else
          result[i-index] = (T) CloneObject(array[i]);
    }

    return result;
  }

  private static object CloneObject(object obj)
  {
    BinaryFormatter formatter = new BinaryFormatter();

    using (MemoryStream stream = new MemoryStream())
    {
      formatter.Serialize(stream, obj);

      stream.Seek(0,SeekOrigin.Begin);

      return formatter.Deserialize(stream);
    }
  }
}


这不是一个完美的解决方案,因为根本没有一种适用于任何类型的对象。

评论


难道不是应该像result [i-index] =(T)...吗?

–唐纳德·伯德
09年7月13日在20:22

是的:)不仅如此。循环边界错误。我会解决的。谢谢!

– Philippe Leybaert
09年7月13日在20:38

#20 楼

您可以参加Microsoft制作的课程:

internal class Set<TElement>
{
    private int[] _buckets;
    private Slot[] _slots;
    private int _count;
    private int _freeList;
    private readonly IEqualityComparer<TElement> _comparer;

    public Set()
        : this(null)
    {
    }

    public Set(IEqualityComparer<TElement> comparer)
    {
        if (comparer == null)
            comparer = EqualityComparer<TElement>.Default;
        _comparer = comparer;
        _buckets = new int[7];
        _slots = new Slot[7];
        _freeList = -1;
    }

    public bool Add(TElement value)
    {
        return !Find(value, true);
    }

    public bool Contains(TElement value)
    {
        return Find(value, false);
    }

    public bool Remove(TElement value)
    {
        var hashCode = InternalGetHashCode(value);
        var index1 = hashCode % _buckets.Length;
        var index2 = -1;
        for (var index3 = _buckets[index1] - 1; index3 >= 0; index3 = _slots[index3].Next)
        {
            if (_slots[index3].HashCode == hashCode && _comparer.Equals(_slots[index3].Value, value))
            {
                if (index2 < 0)
                    _buckets[index1] = _slots[index3].Next + 1;
                else
                    _slots[index2].Next = _slots[index3].Next;
                _slots[index3].HashCode = -1;
                _slots[index3].Value = default(TElement);
                _slots[index3].Next = _freeList;
                _freeList = index3;
                return true;
            }
            index2 = index3;
        }
        return false;
    }

    private bool Find(TElement value, bool add)
    {
        var hashCode = InternalGetHashCode(value);
        for (var index = _buckets[hashCode % _buckets.Length] - 1; index >= 0; index = _slots[index].Next)
        {
            if (_slots[index].HashCode == hashCode && _comparer.Equals(_slots[index].Value, value))
                return true;
        }
        if (add)
        {
            int index1;
            if (_freeList >= 0)
            {
                index1 = _freeList;
                _freeList = _slots[index1].Next;
            }
            else
            {
                if (_count == _slots.Length)
                    Resize();
                index1 = _count;
                ++_count;
            }
            int index2 = hashCode % _buckets.Length;
            _slots[index1].HashCode = hashCode;
            _slots[index1].Value = value;
            _slots[index1].Next = _buckets[index2] - 1;
            _buckets[index2] = index1 + 1;
        }
        return false;
    }

    private void Resize()
    {
        var length = checked(_count * 2 + 1);
        var numArray = new int[length];
        var slotArray = new Slot[length];
        Array.Copy(_slots, 0, slotArray, 0, _count);
        for (var index1 = 0; index1 < _count; ++index1)
        {
            int index2 = slotArray[index1].HashCode % length;
            slotArray[index1].Next = numArray[index2] - 1;
            numArray[index2] = index1 + 1;
        }
        _buckets = numArray;
        _slots = slotArray;
    }

    internal int InternalGetHashCode(TElement value)
    {
        if (value != null)
            return _comparer.GetHashCode(value) & int.MaxValue;
        return 0;
    }

    internal struct Slot
    {
        internal int HashCode;
        internal TElement Value;
        internal int Next;
    }
}


,然后

public static T[] GetSub<T>(this T[] first, T[] second)
    {
        var items = IntersectIteratorWithIndex(first, second);
        if (!items.Any()) return new T[] { };


        var index = items.First().Item2;
        var length = first.Count() - index;
        var subArray = new T[length];
        Array.Copy(first, index, subArray, 0, length);
        return subArray;
    }

    private static IEnumerable<Tuple<T, Int32>> IntersectIteratorWithIndex<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        var firstList = first.ToList();
        var set = new Set<T>();
        foreach (var i in second)
            set.Add(i);
        foreach (var i in firstList)
        {
            if (set.Remove(i))
                yield return new Tuple<T, Int32>(i, firstList.IndexOf(i));
        }
    }


#21 楼

我发现这是执行此操作的最佳方法:

private void GetSubArrayThroughArraySegment() {
  int[] array = { 10, 20, 30 };
  ArraySegment<int> segment = new ArraySegment<int>(array,  1, 2);
  Console.WriteLine("-- Array --");
  int[] original = segment.Array;
  foreach (int value in original)
  {
    Console.WriteLine(value);
  }
  Console.WriteLine("-- Offset --");
  Console.WriteLine(segment.Offset);
  Console.WriteLine("-- Count --");
  Console.WriteLine(segment.Count);

  Console.WriteLine("-- Range --");
  for (int i = segment.Offset; i <= segment.Count; i++)
  {
    Console.WriteLine(segment.Array[i]);
  }
}


希望有帮助!

#22 楼

使用扩展方法:

public static T[] Slice<T>(this T[] source, int start, int end)
    {
        // Handles negative ends.
        if (end < 0)
        {
            end = source.Length + end;
        }
        int len = end - start;

        // Return new array.
        T[] res = new T[len];
        for (int i = 0; i < len; i++)
        {
            res[i] = source[i + start];
        }
        return res;
    }


,您可以使用它

var NewArray = OldArray.Slice(3,7);


#23 楼

来自System.Private.CoreLib.dll的代码:


public static T[] GetSubArray<T>(T[] array, Range range)
{
    if (array == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
    }
    (int Offset, int Length) offsetAndLength = range.GetOffsetAndLength(array.Length);
    int item = offsetAndLength.Offset;
    int item2 = offsetAndLength.Length;
    if (default(T) != null || typeof(T[]) == array.GetType())
    {
        if (item2 == 0)
        {
            return Array.Empty<T>();
        }
        T[] array2 = new T[item2];
        Buffer.Memmove(ref Unsafe.As<byte, T>(ref array2.GetRawSzArrayData()), ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), item), (uint)item2);
        return array2;
    }
    T[] array3 = (T[])Array.CreateInstance(array.GetType().GetElementType(), item2);
    Array.Copy(array, item, array3, 0, item2);
    return array3;
}




#24 楼

它不满足您的克隆要求,但似乎比许多答案要简单:

Array NewArray = new ArraySegment(oldArray,BeginIndex , int Count).ToArray();


#25 楼

public   static   T[]   SubArray<T>(T[] data, int index, int length)
        {
            List<T> retVal = new List<T>();
            if (data == null || data.Length == 0)
                return retVal.ToArray();
            bool startRead = false;
            int count = 0;
            for (int i = 0; i < data.Length; i++)
            {
                if (i == index && !startRead)
                    startRead = true;
                if (startRead)
                {

                    retVal.Add(data[i]);
                    count++;

                    if (count == length)
                        break;
                }
            }
            return retVal.ToArray();
        }