ArraySegment - возврат фактического сегмента C#
Я искал способы вернуть сегмент, который в основном содержится в ArraySegment с точки зрения смещения и подсчета. Хотя ArraySegment содержит полный и оригинальный массив, он просто ограничивает его тем, что любые изменения в сегменте отражаются в оригинале. Проблема или, скажем, ограничение с ArraySegment заключается в том, что он не будет возвращать сам сегмент в целом, и я должен пройти через значения. Как лучше всего вернуть сегмент в целом?
byte[] input = new byte[5]{1,2,3,4,5};
ArraySegment<byte> delimited = new ArraySegment<byte>(input,0,2);
byte[] segment = HERE I NEED SOMETHING THAT WILL RETURN THE SEGMENT i.e. [0,1,2]
Самый важный момент, сегмент не должен быть копией, а должен ссылаться на исходный массив. Если какие-либо изменения в сегменте сделаны, они должны быть отражены в исходном массиве.
Любые советы высоко ценятся, спасибо!
digEmAll: после некоторых ответов от Thomas Levesque и digEmAll
Хорошо, я выполнил некоторые тесты для кода от digEmAll и Thomas, и, к моему удивлению, код работает намного быстрее. Именно то, что я отчаянно искал. Вот результаты.
Construct Size Elements assigned Iterations Time
_______________________________________________________________________________
ArraySegmentWrapper 1500 1500 1000000 396.3 ms
Array.Copy 1500 1500 1000000 4389.04 ms
Как вы можете видеть колоссальную разницу, мне очень ясно, что я буду использовать код для ArraySegment. Ниже приведен код тестирования. Обратите внимание, что это может быть немного предвзятым, поскольку люди будут спорить, почему "новый" был помещен в цикл. Я просто пытаюсь воспроизвести ситуацию, которая у меня есть, и решить ее как можно больше, не перемещая большую часть кода. Это сделало мой день!
namespace ArraySegmentWrapped
{
class Program
{
public static Stopwatch stopWatch = new Stopwatch();
public static TimeSpan span = new TimeSpan();
public static double totalTime = 0.0;
public static int iterations = 1000000;
static void Main(string[] args)
{
int size = 1500;
int startIndex = 0;
int endIndex = 1499;
byte[] array1 = new byte[size];
byte[] array2 = null;
for (int index = startIndex; index < size; index++)
{
array1[index] = (byte)index;
}
ArraySegmentWrapper<byte> arraySeg;
for (int index = 0; index < iterations; index++)
{
stopWatch.Start();
arraySeg = new ArraySegmentWrapper<byte>(array1, startIndex, endIndex);
stopWatch.Stop();
totalTime += stopWatch.Elapsed.TotalMilliseconds;
}
Console.WriteLine("ArraySegment:{0:F6}", totalTime / iterations);
stopWatch.Reset();
totalTime = 0.0;
for (int index = 0; index < iterations; index++)
{
stopWatch.Start();
array2 = new byte[endIndex - startIndex + 1];
Array.Copy(array1, startIndex, array2, 0, endIndex);
stopWatch.Stop();
totalTime += stopWatch.Elapsed.TotalMilliseconds;
}
Console.WriteLine("Array.Copy:{0:F6}", totalTime / iterations);
}
}
// Code for ArraySegmentWrapper goes here
}
ЭТАЛОНЫ ДОСТУПА (Обновлено) Итак, после того, что Thomas Levesque указал на тесты и сказал, что доступ к простым массивам будет быстрее по сравнению с ArraySegment, он был совершенно прав. Но с digEmAll указав, что я должен тестировать в режиме выпуска (извините за старую ошибку тестирования в режиме отладки), я оставил код почти таким же, как указано выше (итерации сокращены на два нуля - не мог ждать очень долго для вывода в давай, извини) и некоторые модификации для доступа к тому же количеству элементов, Ниже то, что я получил.
Construct Size Elements accessed Iterations Time
_______________________________________________________________________________
ArraySegmentWrapper 1500 1500 1000000 5268.3 ms
Array.Copy 1500 1500 1000000 4812.4 ms
Сделан вывод о том, что, хотя распределение выполняется очень быстро, доступ через ArraySegments замедляется.
4 ответа
Исходя из предположения Томаса Левеска, я построил простую ArraySegmentWrapper<T>
класс для использования таким образом:
static void Main(string[] args)
{
int[] arr = new int[10];
for (int i = 0; i < arr.Length; i++)
arr[i] = i;
// arr = 0,1,2,3,4,5,6,7,8,9
var segment = new ArraySegmentWrapper<int>(arr, 2, 7);
segment[0] = -1;
segment[6] = -1;
// now arr = 0,1,-1,3,4,5,6,7,-1,9
// this prints: -1,3,4,5,6,7,-1
foreach (var el in segment)
Console.WriteLine(el);
}
Реализация:
public class ArraySegmentWrapper<T> : IList<T>
{
private readonly ArraySegment<T> segment;
public ArraySegmentWrapper(ArraySegment<T> segment)
{
this.segment = segment;
}
public ArraySegmentWrapper(T[] array, int offset, int count)
: this(new ArraySegment<T>(array, offset, count))
{
}
public int IndexOf(T item)
{
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
if (Equals(segment.Array[i], item))
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 >= this.Count)
throw new IndexOutOfRangeException();
return this.segment.Array[index + this.segment.Offset];
}
set
{
if (index >= this.Count)
throw new IndexOutOfRangeException();
this.segment.Array[index + this.segment.Offset] = value;
}
}
public void Add(T item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Contains(T item)
{
return this.IndexOf(item) != -1;
}
public void CopyTo(T[] array, int arrayIndex)
{
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
{
array[arrayIndex] = segment.Array[i];
arrayIndex++;
}
}
public int Count
{
get { return this.segment.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
throw new NotSupportedException();
}
public IEnumerator<T> GetEnumerator()
{
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
yield return segment.Array[i];
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
РЕДАКТИРОВАТЬ:
Как отмечает @JeppeStigNielsen в комментариях, начиная с.NET 4.5 ArraySegment<T>
инвентарь IList<T>
Я использую следующий набор методов расширения для работы с сегментами массива:
#region ArraySegment related methods
public static ArraySegment<T> GetSegment<T>(this T[] array, int from, int count)
{
return new ArraySegment<T>(array, from, count);
}
public static ArraySegment<T> GetSegment<T>(this T[] array, int from)
{
return GetSegment(array, from, array.Length - from);
}
public static ArraySegment<T> GetSegment<T>(this T[] array)
{
return new ArraySegment<T>(array);
}
public static IEnumerable<T> AsEnumerable<T>(this ArraySegment<T> arraySegment)
{
return arraySegment.Array.Skip(arraySegment.Offset).Take(arraySegment.Count);
}
public static T[] ToArray<T>(this ArraySegment<T> arraySegment)
{
T[] array = new T[arraySegment.Count];
Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
return array;
}
#endregion
Вы можете использовать их следующим образом:
byte[] input = new byte[5]{1,2,3,4,5};
ArraySegment<byte> delimited = input.GetSegment(0, 2);
byte[] segment = delimited.ToArray();
C# (и.NET в целом) не позволяет создавать стандартную ссылку на массив, которая "указывает" на внутреннюю часть другого массива. Таким образом, вам нужно либо изменить свои API-интерфейсы-потребители, чтобы они могли работать с экземплярами ArraySegment, либо вам нужно создать копию данных и затем скопировать изменения обратно после работы с этой копией. В любом случае это, как правило, более безопасный подход, поскольку передача ссылок на массив нарушает изоляцию и затрудняет отслеживание ошибок по мере увеличения числа потребителей массива. Создание новых экземпляров массива и копирование значений относительно дешево в.NET, поскольку массивы не очень велики по размеру, поэтому влияние на производительность здесь, как правило, незначительно.
Если у вас проблемы с производительностью и вам необходимо микрооптимизацию, я бы порекомендовал использовать небезопасный код C# (где вы можете исправить ссылку на массив и передавать указатели) или вытащить критичный для производительности код в C++/CLI. сборка, где вы можете делать вычисления с неуправляемой памятью. Я бы порекомендовал сначала профилировать код, чтобы убедиться, что это действительно ваше узкое место. Я не могу не подчеркнуть, что вы не должны бояться выделения новой памяти в.NET, поскольку природа кучи сжатия GC означает, что частые небольшие выделения дешевле, чем в C (где распределение памяти должно быть приспособлено для возможной фрагментации кучи)..)
Проверьте ответ, который я разместил на эту тему здесь.
По сути, все, что вам нужно сделать, это привести ArraySegment к IList, чтобы получить ожидаемую функциональность.