【问题标题】:How do I clone a range of array elements to a new array?如何将一系列数组元素克隆到新数组?
【发布时间】:2010-10-30 22:15:46
【问题描述】:

我有一个包含 10 个元素的数组 X。我想创建一个新数组,其中包含 X 中从索引 3 开始并以索引 7 结束的所有元素。当然,我可以轻松编写一个循环来为我做这件事,但我想让我的代码尽可能干净. C#中有没有可以为我做的方法?

类似(伪代码):

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

Array.Copy 不符合我的需求。我需要将新数组中的项目克隆。 Array.copy 只是 C 风格的 memcpy 等价物,这不是我要找的。​​p>

【问题讨论】:

标签: c# arrays .net clone deep-copy


【解决方案1】:

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

如果您使用的是 .NET 3.5,您可以使用 LINQ:

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

但是这样效率会低一些。

有关更具体情况的选项,请参阅this answer 的类似问题。

【讨论】:

  • +1 我也喜欢这种变化。乔恩,您能否详细说明为什么这被认为效率较低?
  • @Jon:为了匹配这个问题,那不是“Take(5)”吗? @Ian:Array.Copy 方法不涉及枚举器,并且很可能是直接的内存复制...
  • @Marc:确实如此。太多的问题略读:)
  • @Ian:LINQ 方法引入了两个间接级别(迭代器),必须显式跳过项目,并且事先不知道最终数组的大小。考虑采用 200 万个元素数组的后半部分:一个简单的“创建目标数组,复制”方法将只复制所需的块,而不涉及其他元素,并且一次完成。 LINQ 方法将遍历数组,直到到达起点,然后开始取值,建立缓冲区(增加缓冲区大小并定期复制)。效率低得多。
  • 如果5是EndIndexm,那么正确的问题是array.Skip(3).Take(5-3+1).ToArray(); IE。 array.Skip(StartIndex).Take(EndIndex-StartIndex+1).ToArray();
【解决方案2】:

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

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 在大多数情况下很难信任。

【讨论】:

  • (显然使用结束索引而不是长度是一个简单的更改;我已经“按原样”发布,因为这是更“典型”的用法)
  • 那么……艰难;它没有那样做......你可能需要使用序列化来实现类似的东西
  • 查看我对一些替代方案的回答和几个实现的链接。对子数组执行此操作的部分非常简单,您真正想要的是 cloning 位,这是一个复杂且有点开放的问题,完全取决于您对“正确”的期望' 行为应该是.
  • 这很好。尤其要指出 ICloneable 是不可靠的,因为哦,它曾经是。
  • 感谢您强调 C# 中的深度克隆问题。真的很遗憾,就像deep copying is a fundamental operation
【解决方案3】:

Array.ConstrainedCopy 会起作用。

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

【讨论】:

  • 那只是复制数据;它不会创建新数组等;如果数组是新的,我们可以使用更高效的 Array.Copy(不需要额外的检查/回滚)。
  • 没错,但是创建一个新的Array只是一行代码,不需要新的方法。我同意 Array.Copy 也可以。
【解决方案4】:

我认为您要查找的代码是:

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

【讨论】:

  • 我想我在这里交了一些好朋友......和你一样的答案;)我被投了很多票!哈哈!!总之,好时光好时光。
【解决方案5】:

你可以很容易地做到这一点;

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

【讨论】:

  • 不,bar 仍然为空。 Array.Copy 不会神奇地创建一个新数组,特别是因为 bar 没有通过 ref 或 out 传递。
  • 哦,嘿,你说得对,我这样做很匆忙,但是,嘿,也许当你的写作批评时你应该进行更正,建设性批评对每个人都更有用。所以在那个 array.copy 之前你做一个“bar = new object[7];”
【解决方案6】:

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

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

【讨论】:

  • @Robert:不,不是。尝试改用 ArraySegment,你会发现你既不能通过索引访问项目,也不能遍历项目。
【解决方案7】:

我看到你想做克隆,而不仅仅是复制参考。 在这种情况下,您可以使用.Select 将数组成员投影到它们的克隆中。 例如,如果你的元素实现了IClonable,你可以这样做:

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

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

【讨论】:

  • 这样更优雅。
  • 这正是我想要的。这适用于任何IEnumerable。我可以得到一个IEnumerableIListIArray 等等......如果我需要的话,我可以毫不费力地内联。如果我不需要深拷贝,我只需删除Select。删除SkipTake 允许我控制范围。或者,我可以将其与 SkipWhile 和/或 TakeWhile 混合使用。
【解决方案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åvard Stranden’s Copyable library 进行反思以完成所需的繁重工作。

using OX.Copyable;

public static T[] DeepCopySubArray<T>(
    this T[] data, int index, int length)
{
    T[] result = new T[length];
    for (int i = 0; i < length; i++)
    { 
        var original = data[index + i];
        if (original != null)
            result[i] = (T)original.Copy();            
    return result;
}

请注意,OX.Copyable 实现适用于以下任何一种:

但是,要使自动复制起作用,例如必须满足以下语句之一:

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

所以这应该涵盖您遇到的几乎所有情况。如果您正在克隆对象,其中子图包含数据库连接或文件/流句柄等内容,那么您显然会遇到问题,但对于任何通用深层副本都是如此。

如果您想使用其他深拷贝方法而不是 article lists several others,那么我建议您不要尝试自己编写。

【讨论】:

  • 第一个可能是想要的解决方案,因为他要求克隆。请注意,使用 Copy 方法,您甚至可能不必检查 null,因为它是一种扩展方法,如果方法本身已经完成了该操作。值得一试。
  • 是的,我注意到空检查但不想混淆 OP,以防他没有阅读源代码。
  • 旁注:GitHub 上最新版本的 Copyable 不要求对象具有无参数构造函数。 :) 见github.com/havard/copyable
【解决方案9】:

没有一种方法可以满足您的需求。您需要为数组中的类提供一个克隆方法。那么,如果 LINQ 是一个选项:

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

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

【讨论】:

    【解决方案10】:

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

    让我们采用“尽力而为”的方法:使用 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)... 吗?
    • 是的 :) 不仅如此。循环边界错误。我会修复它。谢谢!
    【解决方案11】:

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

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

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

    【讨论】:

    • 序列化是否调用你的构造函数取决于具体的序列化器。有些会,有些不会。但是那些通常不提供回调支持以允许您进行任何所需的修复。
    • 这突出了序列化的另一个摩擦点:您必须提供默认构造函数。
    【解决方案12】:

    Array.ConstrainedCopy怎么样:

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

    以下是我的原帖。它不会工作

    你可以使用Array.CopyTo:

    int[] ArrayOne = new int[8] {1,2,3,4,5,6,7,8};
    int[] ArrayTwo = new int[5];
    ArrayOne.CopyTo(ArrayTwo,3); //starts copy at index=3 until it reaches end of
                                 //either array
    

    【讨论】:

      【解决方案13】:

      您是否考虑过使用ArraySegment

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

      【讨论】:

      • 它可能做你想要的,但它不支持默认的数组语法,也不支持IEnumerable,所以它不是特别干净。
      • 这需要更多的支持。在我自己的 exp 中,ArraySegment 复制也稍微快一些(毕竟我使用数组来处理速度关键的东西)..
      • @AlexBlack 从.NET 4.5 开始,它实现了IEnumerable&lt;T&gt; 和其他各种有用的接口。
      • 你会如何使用ArraySegment 来回答原来的问题?
      • @CraigMcQueen - 尝试以下单行方法:IList&lt;T&gt; newArray = (IList&lt;T&gt;)new ArraySegment&lt;T&gt;(oldArray, beginIndex, endIndex);
      【解决方案14】:

      这个怎么样:

      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 接口,但应该这样做。

      【讨论】:

        【解决方案15】:

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

        MyArray.ToList&lt;TSource&gt;().GetRange(beginningIndex, endIndex).ToArray()

        这有点开销,但它可能会减少不必要的方法。

        【讨论】:

          【解决方案16】:

          以下代码在一行中完成:

          // 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。这是我的首选方式。
          • 它仍然没有克隆源......但无论如何这是一个好方法
          • 它应该克隆源,因为 ToArray:(1)创建一个新数组,(2)执行 Array.Copy。最后 Source 和 Slice 是两个独立的对象。该方法是正确的,但是,我更喜欢 Array.Copy:referencesource.microsoft.com/#mscorlib/system/collections/…
          【解决方案17】:
          string[] arr = { "Parrot" , "Snake" ,"Rabbit" , "Dog" , "cat" };
          
          arr = arr.ToList().GetRange(0, arr.Length -1).ToArray();
          

          【讨论】:

            【解决方案18】:
            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();
                    }
            

            【讨论】:

              【解决方案19】:

              你可以参加微软制作的课程:

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

              【讨论】:

                【解决方案20】:

                这是我发现的最佳方法:

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

                希望对你有帮助!

                【讨论】:

                  【解决方案21】:

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

                  参考资料:

                  【讨论】:

                  • 这应该是现在认可的答案。
                  【解决方案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】:

                        在 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
                        

                        【讨论】:

                          猜你喜欢
                          • 2020-06-17
                          • 2013-02-19
                          • 2016-04-08
                          • 2018-02-24
                          • 1970-01-01
                          • 1970-01-01
                          • 2015-06-27
                          • 2019-12-05
                          相关资源
                          最近更新 更多