Как отмечается в комментариях и других опубликованных ответах, здесь есть фундаментальная проблема с вашим кодом:
public Metadata Meta
{
get { return new Metadata(GetSubarray(0, 2)); }
set { UpdateData(0, value.GetData()); }
}
Тот факт, что вы создаете новый объект Metadata
, заставляет это утверждение не выполнять то, чтовы думаете, что он делает:
dataObj.Meta.Prop0 = 8;
Получатель свойства Meta
создает новый объект Meta
, который затем используется для установки свойства Prop0
. Но этот объект Meta
не имеет связи с объектом DataObject
. Вызывается установщик Prop0
объекта, а затем тот объект Meta
немедленно забывается. Ссылка на него отсутствует, в конечном итоге она будет собрана сборщиком мусора и не повлияет на объект DataObject
.
Учитывая вашу текущую реализацию DataObject
, один из способов исправить кодчтобы получить Meta
объект в переменную, установить свойство Prop0
, а затем снова установить свойство Meta
:
Meta meta = dataObj.Meta;
meta.Prop0 = 8;
dataObj.Meta = meta;
Теперь, честно говоря, я думаю, что этот общий дизайн простосломана. Идея хранить эти значения в массиве, а затем пытаться отобразить элементы массива на отдельные свойства, просто напрашивается на неприятности. Это приведет именно к тому типу ошибок, которые у вас есть, и затруднит понимание и использование объектов. Если вы do действительно хотите использовать массив для поддержки свойств, то вам действительно следует по крайней мере убедиться, что вы когда-либо выделяете массив и объекты только один раз, чтобы для вызывающей стороны свойства действовали как обычныесвойства вместо того, чтобы иметь это странное «вы должны сохранить объект, изменить его, а затем снова установить значение свойства» семантика. Например, что-то вроде этого:
public class DataObject
{
private struct ByteSpan : IList<byte>
{
private readonly byte[] _data;
private readonly int _start;
private readonly int _count;
public ByteSpan(byte[] data, int start, int count)
{
_data = data;
_start = start;
_count = count;
}
public byte this[int index]
{
get
{
if (index < 0 || index >= _count) throw new IndexOutOfRangeException();
return _data[_start + index];
}
set
{
if (index < 0 || index >= _count) throw new IndexOutOfRangeException();
_data[_start + index] = value;
}
}
public int Count => _count;
public bool IsReadOnly => false;
public void Add(byte item) => throw new NotImplementedException();
public void Clear() => throw new NotImplementedException();
public bool Contains(byte item) => this.Any(b => b == item);
public void CopyTo(byte[] array, int arrayIndex) => Array.Copy(_data, _start, array, arrayIndex, _count);
public IEnumerator<byte> GetEnumerator() => _data.Skip(_start).Take(_count).GetEnumerator();
public int IndexOf(byte item)
{
int indexOf = ((IList<byte>)_data).IndexOf(item) - _start;
if (indexOf < 0) return -1;
if (indexOf >= _count) return -1;
return indexOf;
}
public void Insert(int index, byte item) => throw new NotImplementedException();
public bool Remove(byte item) => throw new NotImplementedException();
public void RemoveAt(int index) => throw new NotImplementedException();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
private readonly byte[] _data;
private readonly IList<byte> _property1;
public DataObject(byte[] objectData)
{
_data = objectData;
Meta = new Metadata(new ByteSpan(_data, 0, 2));
_property1 = new ByteSpan(_data, 2, 5);
}
public byte[] GetData()
{
return _data;
}
public Metadata Meta { get; }
public IList<byte> Property1
{
get => _property1;
set => CopyIList(value, _property1);
}
private void CopyIList<T>(IList<T> source, IList<T> destination)
{
if (source.Count != destination.Count) throw new ArgumentException("source and destination must be the same length");
for (int i = 0; i < source.Count; i++)
{
destination[i] = source[i];
}
}
}
public class Metadata
{
private IList<byte> _metadata;
public Metadata(IList<byte> metadata)
{
_metadata = metadata;
}
public IList<byte> GetData()
{
return _metadata;
}
public byte Prop0
{
get => _metadata[0];
set => _metadata[0] = value;
}
public byte Prop1
{
get => _metadata[1];
set => _metadata[1] = value;
}
}
Для выполнения вышесказанного необходимо немного изменить интерфейс DataObject
. В частности, вместо возврата массивов он должен возвращать IList<T>
объектов. Это позволяет использовать тот же массив в качестве базового хранилища для всех представленных свойств массива.
(Кроме того, в приведенном выше примере используется пользовательский тип ByteSpan
, который реализует IList<T>
который представляет собой подмножество базового массива данных. Если вы можете использовать .NET Core, вы найдете эквивалентную Span<T>
структуру, которую можно использовать вместо этого.)
Если вы настаиваете на возвратеЗначения массива вместо IList<byte>
, тогда вам придется немного изменить вышеприведенное, чтобы свойство копировало значения из предоставленного массива в базовый массив данных. Аналогично, получатели должны будут скопировать значения. К сожалению, это приведет к чему-то очень похожему на то, с чем вы уже имеете дело, в том, что массив, возвращаемый свойством, не может напрямую изменить базовый массив данных. Если вызывающая сторона только извлекает значение массива и изменяет его, не устанавливая свойство снова в этот же массив, базовый массив данных не будет изменен, и у вас будет точно такая же проблема.
Даже свыше версии, где свойства используют IList<byte>
вместо byte[]
, установщик все еще должен скопировать данный список в собственный список свойства, потому что список, передаваемый установщику, может быть или не быть оригинальным. Если это не так, то присвоение ссылки на список свойств для этого приведет к отключению свойства от основного хранилища. Только копируя значения из одного в другое, список свойств может оставаться прежним и продолжать ссылаться на исходный базовый массив данных.
Но, по крайней мере, используя IList<byte>
вместо byte[]
,вызывающая сторона не требуется для повторного задания значения свойства после изменения списка. Изменение списка само по себе изменит базовый массив данных.
Я настоятельно рекомендую не использовать массив данных в качестве базового хранилища, но если вы это сделаете, я так же настоятельно рекомендую вам сделать используйте что-то вроде типов ByteSpan
или Span<T>
, чтобы обернуть базовый массив данных в объект, которым вызывающие могут манипулировать, непосредственно воздействуя на базовое хранилище.