Обновление свойства другого свойства в классе - PullRequest
0 голосов
/ 09 ноября 2019

У меня очень большой вопрос для начинающих: я уверен, что мне здесь не хватает чего-то невероятно фундаментального

У меня есть упрощенная версия кода (использование массивов и т. Д. Только для иллюстрации):

DataObject dataObj = new DataObject(new byte[7]{1,1,1,1,1,1,1));

dataObj.Meta.Prop0 = 8;
dataObj.Property1 = new byte[5]{8,8,8,8,8};

CollectionAssert.AreEqual(new byte[7]{8,1,8,8,8,8,8}, dataObj.GetData()); //<--- Fails.  array[0] is still 1.
Assert.AreEqual(8, dataObj.Meta.Prop0); //<--- Fails.  Prop0 never updated (still 1)
Assert.AreEqual(new byte[5]{8,8,8,8,8}, dataObj.Property1); //<--- OK.

Почему dataObj.Meta.Prop0 не возвращает правильное значение? Я обновляю _data в DataObject.Meta установщике, однако, если я помещаю точку останова в метод Metadata.GetData() (который вызывается из DataObject.Meta установщика), он никогда не срабатывает ..

Ниже приведена подробная информация о реализации класса DataObject (обновлена ​​с реализациями GetSubrray() и UpdateData() в соответствии с запросом):

    public class DataObject
    {
        private byte[] _data;

        public DataObject(byte[] objectData)
        {
            _data = objectData;
        }

        public byte[] GetData()
        {
            return _data;
        }

        public Metadata Meta
        {
            get
            {
                return new Metadata(GetSubarray(0, 2));
            }
            set
            {
                UpdateData(0, value.GetData());
            }
        }

        public byte[] Property1
        {
            get
            {
                return GetSubarray(2, 5);
            }
            set
            {
                UpdateData(2, value);
            }
        }

        private void UpdateData(int start, byte[] data)
        {
            Array.ConstrainedCopy(data, 0, _data, start, data.Length);
        }

        private byte[] GetSubarray(int start, int length)
        {
            byte[] arr = new byte[length];
            Array.ConstrainedCopy(_data, start, arr, 0, length);
            return arr;
        }
    }

    public class Metadata
    {
        private byte[] _metadata;

        public Metadata(byte[] metadata)
        {
            _metadata = metadata;
        }

        public byte[] GetData()
        {
            return _metadata;
        }

        public byte Prop0
        {
            get
            {
                return _metadata[0];
            }
            set
            {
                _metadata[0] = value;
            }
        }

        public byte Prop1
        {
            get
            {
                return _metadata[1];
            }
            set
            {
                _metadata[1] = value;
            }
        }
    }

Ответы [ 2 ]

0 голосов
/ 09 ноября 2019

Как отмечается в комментариях и других опубликованных ответах, здесь есть фундаментальная проблема с вашим кодом:

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>, чтобы обернуть базовый массив данных в объект, которым вызывающие могут манипулировать, непосредственно воздействуя на базовое хранилище.

0 голосов
/ 09 ноября 2019

Извините, но дизайн не так! У вас есть копия байта [] основных данных. пример, когда вы говорите

return new Metadata(_data.Subarray(0,2));

У вас есть те же данные, но копия основных данных. это копия. Не ссылается на основной объект!

private byte[] GetSubarray(int start, int length)
        {
            byte[] arr = new byte[length];
            Array.ConstrainedCopy(_data, start, arr, 0, length);
            return arr;
        }

Поэтому, когда вы обновляете Prop0 Prop1 и т. Д. В мета-байте [], вы думаете, что основные данные будут обновлены. Извините, но не. Это копия байтов данных .. не ссылка.

Если вы хотите обновить основной объект, вы должны переместить эти свойства внутрь объекта Main. Не копируйте что-либо.

Переместите ваш Prop0 Prop1 и т. Д. Внутрь основного объекта. обновить основные _данные напрямую. Не копия объекта.


public class DataObject
    {
        private byte[] _data;

        public DataObject(byte[] objectData)
        {
            _data = objectData;
        }

        public byte[] GetData()
        {
            return _data;
        }

        public Metadata Meta {
            get {
                return new Metadata(GetSubarray(0, 2));
            }
            set {
                UpdateData(0, value.GetData());
            }
        }

        public byte[] Property1 {
            get {
                return GetSubarray(2, 5);
            }
            set {
                UpdateData(2, value);
            }
        }

        public byte Prop0 {
            get {
                return Meta.GetData()[0];
            }
            set {
                UpdateData(0, new byte[]{ value});
            }
        }

        public byte Prop1 {
            get {
                return Meta.GetData()[1];
            }
            set {
                UpdateData(1, new byte[] { value });
            }
        }

        private void UpdateData(int start, byte[] data)
        {
            Array.ConstrainedCopy(data, 0, _data, start, data.Length);
        }

        private byte[] GetSubarray(int start, int length)
        {
            byte[] arr = new byte[length];
            Array.ConstrainedCopy(_data, start, arr, 0, length);
            return arr;
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...