public override void ApplyDiff(ITrwSerializationDiffApplier applier, TObj target, ITrwDiff diff, TrwDiffDirection direction)
        {
            if (typeof(TObj).IsValueType)
            {
                base.ApplyDiff(applier, target, diff, direction);
                return;
            }

            if (!(diff is MutateObjectTrwDiff odiff))
            {
                throw new ArgumentException("Diff of type MutateObjectTrwDiff is expected.");
            }
            if (odiff.AddedProperties.Any() || odiff.RemovedProperties.Any())
            {
                throw new ArgumentException("Diffs with added or removed properties are not applicable.");
            }
            foreach (var kvp in odiff.DiffedProperties)
            {
                if (!TryGetProp(target, kvp.First, out var prop))
                {
                    continue;
                }
                var forward = direction == TrwDiffDirection.Forward;
                switch (kvp.Second)
                {
                case ReplaceValueTrwDiff replaceDiff:
                    var newValue = (TValue)applier.FromDynamic(GetPropType(prop), forward ? replaceDiff.NewValue : replaceDiff.OldValue);
                    SetPropValue(target, target, prop, newValue);
                    break;

                default:
                    var value = GetPropValue(target, prop);
                    applier.ApplyDiff(value, kvp.Second, direction);
                    break;
                }
            }
        }
        public override void ApplyDiff(ITrwSerializationDiffApplier applier, TArray target, ITrwDiff diff, TrwDiffDirection direction)
        {
            if (!(diff is MutateArrayTrwDiff adiff))
            {
                throw new ArgumentException("Diff of type MutateArrayTrwDiff expected.");
            }
            TItem[] result;
            switch (direction)
            {
            case TrwDiffDirection.Forward:
            {
                result = new TItem[GetCount(target) + adiff.AddedItems.Count - adiff.RemovedItems.Count];
                foreach (var pair in adiff.AddedItems)
                {
                    result[pair.First] = (TItem)applier.FromDynamic(typeof(TItem), pair.Second);
                }
                foreach (var pair in adiff.MovedItems)
                {
                    result[pair.Second] = GetItem(target, pair.First);
                }
                foreach (var pair in adiff.DiffedItems)
                {
                    var item = GetItem(target, pair.First.First);
                    applier.ApplyDiff(item, pair.Second, direction);
                    result[pair.First.Second] = item;
                }
                break;
            }

            case TrwDiffDirection.Backward:
            {
                result = new TItem[GetCount(target) - adiff.AddedItems.Count + adiff.RemovedItems.Count];
                foreach (var pair in adiff.RemovedItems)
                {
                    result[pair.First] = (TItem)applier.FromDynamic(typeof(TItem), pair.Second);
                }
                foreach (var pair in adiff.MovedItems)
                {
                    result[pair.First] = GetItem(target, pair.Second);
                }
                foreach (var pair in adiff.DiffedItems)
                {
                    var item = GetItem(target, pair.First.Second);
                    applier.ApplyDiff(item, pair.Second, direction);
                    result[pair.First.First] = item;
                }
                break;
            }

            default:
                throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
            }
            foreach (var i in adiff.UnaffectedItems)
            {
                result[i] = GetItem(target, i);
            }
            for (var i = 0; i < result.Length; i++)
            {
                var item        = result[i];
                var targetIndex =
                    typeof(TItem).IsValueType
                        ? typeof(IEquatable <TItem>).IsAssignableFrom(typeof(TItem))
                            ? EnumerateItems(target).IndexOf(x => ((IEquatable <TItem>)x).Equals(item))
                            : EnumerateItems(target).IndexOf(x => Equals(x, item))
                        : EnumerateItems(target).IndexOf(x => ReferenceEquals(x, item));
                if (targetIndex.HasValue)
                {
                    if (targetIndex.Value == i)
                    {
                        continue;
                    }
                    RemoveAt(target, targetIndex.Value);
                    Insert(target, i, item);
                }
                else
                {
                    Insert(target, i, item);
                }
            }
            for (var i = GetCount(target) - 1; i >= result.Length; i--)
            {
                RemoveAt(target, i);
            }
        }