public override object Read(object value, ProtoReader source)
        {
            Array result = null;

            int[] lengths           = new int[_rank];
            int[] indexes           = new int[_rank];
            int   deepestRank       = 0;
            int   deepestRankLength = 0;

            int totalLength = 1;

            _listHelpers.Read(
                () =>
            {
                if (source.TryReadFieldHeader(ListHelpers.FieldLength))
                {
                    int length             = source.ReadInt32();
                    lengths[deepestRank++] = length;
                    totalLength           *= length;

                    return(true);
                }
                return(false);
            },
                () =>
            {
                // count
                if (deepestRank != _rank)
                {
                    ThrowWrongRank();
                }
                // last
                deepestRank--;

                deepestRankLength = lengths[deepestRank];

                if (totalLength > _readLengthLimit)
                {
                    ArrayDecorator.ThrowExceededLengthLimit(totalLength, _readLengthLimit);
                }

                // TODO use same instance when length equals and no AppendCollection, don't forget to NoteObject even for the same instance

                result = Read_CreateInstance(value, lengths, out indexes[0], source);
            },
                v =>
            {
                result.SetValue(v, indexes);
                //Debug.WriteLine(string.Join(",", indexes.Select(x => x.ToString()).ToArray()));
                int newIndex = ++indexes[deepestRank];
                if (newIndex >= deepestRankLength)
                {
                    int rankIndex = deepestRank;
                    while (rankIndex > 0)
                    {
                        indexes[rankIndex] = 0;
                        --rankIndex;
                        indexes[rankIndex]++;

                        if (indexes[rankIndex] < lengths[rankIndex])
                        {
                            break;
                        }
                    }
                }
            },
                source);
            return(result);
        }
        protected override void EmitRead(AqlaSerializer.Compiler.CompilerContext ctx, AqlaSerializer.Compiler.Local valueFrom)
        {
            var g = ctx.G;

            using (ctx.StartDebugBlockAuto(this))
                using (Compiler.Local value = ctx.GetLocalWithValueForEmitRead(this, valueFrom))
                    using (Compiler.Local result = ctx.Local(_arrayType, true))
                        using (Compiler.Local lengthTemp = ctx.Local(typeof(int)))
                            using (Compiler.Local deepestRank = ctx.Local(typeof(int), true))
                                using (Compiler.Local totalLength = ctx.Local(typeof(int)))
                                {
                                    var lengths   = Enumerable.Range(0, _rank).Select(x => g.ctx.Local(typeof(int))).ToArray();
                                    var indexes   = Enumerable.Range(0, _rank).Select(x => g.ctx.Local(typeof(int), true)).ToArray();
                                    var indexesOp = indexes.Select(x => (Operand)x).ToArray();

                                    var deepestRankLength = lengths[lengths.Length - 1];

                                    g.Assign(totalLength, 1);

                                    _listHelpers.EmitRead(
                                        ctx.G,
                                        (onSuccess, onFail) =>
                                    {
                                        using (ctx.StartDebugBlockAuto(this, "meta"))
                                        {
                                            g.If(g.ReaderFunc.TryReadFieldHeader_bool(ListHelpers.FieldLength));
                                            {
                                                g.Assign(lengthTemp, g.ReaderFunc.ReadInt32());
                                                g.Switch(deepestRank);
                                                {
                                                    for (int i = 0; i < _rank; i++)
                                                    {
                                                        g.Case(i);
                                                        {
                                                            g.Assign(lengths[i], lengthTemp);
                                                        }
                                                        g.Break();
                                                    }

                                                    g.DefaultCase();
                                                    EmitThrowWrongRank(g);
                                                }
                                                g.End();
                                                g.Increment(deepestRank);
                                                g.Assign(totalLength, totalLength.AsOperand * lengthTemp.AsOperand);
                                                onSuccess();
                                            }
                                            g.Else();
                                            {
                                                onFail();
                                            }
                                            g.End();
                                        }
                                    },
                                        () =>
                                    {
                                        using (ctx.StartDebugBlockAuto(this, "prepareInstance"))
                                        {
                                            g.If(deepestRank.AsOperand != _rank);
                                            {
                                                EmitThrowWrongRank(g);
                                            }
                                            g.End();

                                            //g.Decrement(deepestRank); - not used

                                            g.If(totalLength.AsOperand > _readLengthLimit);
                                            {
                                                ArrayDecorator.EmitThrowExceededLengthLimit(g, totalLength, _readLengthLimit);
                                            }
                                            g.End();

                                            ctx.MarkDebug("// length read, creating instance");
                                            EmitRead_CreateInstance(g, value, lengths, indexes[0], result);
                                        }
                                    },
                                        v =>
                                    {
                                        using (ctx.StartDebugBlockAuto(this, "add"))
                                        {
                                            g.Assign(result.AsOperand[indexesOp], v);

                                            var newIndex = indexes[_rank - 1];
                                            g.Increment(newIndex);
                                            g.If(newIndex.AsOperand >= deepestRankLength.AsOperand);
                                            {
                                                // unwrapped loop
                                                var breakLabel = g.DefineLabel();
                                                int rankIndex  = _rank - 1;
                                                while (rankIndex > 0)
                                                {
                                                    g.Assign(indexes[rankIndex], 0);
                                                    --rankIndex;
                                                    g.Increment(indexes[rankIndex]);
                                                    g.If(indexes[rankIndex].AsOperand < lengths[rankIndex].AsOperand);
                                                    {
                                                        g.Goto(breakLabel);
                                                    }
                                                    g.End();
                                                }
                                                g.MarkLabel(breakLabel);
                                            }
                                            g.End();
                                        }
                                    });

                                    foreach (Local local in indexes.Concat(lengths))
                                    {
                                        local.Dispose();
                                    }

                                    if (EmitReadReturnsValue)
                                    {
                                        ctx.LoadValue(result);
                                    }
                                    else
                                    {
                                        g.Assign(value, result);
                                    }
                                }
        }