public ConfigProxy <T> ReadFrom <T>(T fallbackValue, string subsection, [CallerMemberName] string key = "")
        {
            ConfigProxy <T> value     = null;
            var             valueType = typeof(T).IsArray
                ? typeof(T).GetElementType()
                : typeof(T);

            if (GetValueMarshaller(valueType) != null)
            {
                value = ReadValueFrom(fallbackValue, subsection, key);
            }
            else if (GetObjectMarshaller(typeof(T)) != null)
            {
                value = ReadObjectFrom(fallbackValue, subsection, key);
            }
            else
            {
                value = new ConfigProxy <T>(fallbackValue, null, false, delegate { }, delegate { }, delegate { });
            }

            return(value);
        }
        ConfigProxy <T> readObjectFrom <T>(T fallbackValue, string subsection, Type valueType, [CallerMemberName] string key = "")
        {
            var path = $".{subsection}.{key}.{key}";

            if (_proxyPaths.Contains(path))
            {
                throw new InvalidOperationException("Значение с данным ключем уже было прочитано");
            }

            var marshaller = GetObjectMarshaller(valueType);
            var section    = new Section(new Section(_config.RootSection, subsection), key);

            if (marshaller == null)
            {
                throw new NotSupportedException($"Тип {typeof(T)} не поддерживается.");
            }
            else if (!section.IsCorrect)
            {
                throw new ArgumentException();
            }

            T   readValue     = fallbackValue;
            var validKVPFound = false;

            foreach (var kvp in _config.KVPs)
            {
                if (kvp.Section.Equals(section))
                {
                    var configProxy = getProxy();
                    var isParsed    = marshaller.TryUnpack(configProxy, valueType, out dynamic parsedValue);
                    readValue     = isParsed ? parsedValue : fallbackValue;
                    validKVPFound = isParsed;
                    if (validKVPFound)
                    {
                        break;
                    }
                }
            }
            if (!validKVPFound)
            {
                tryAppendKVP();
            }

            _proxyPaths.Add(path);
            ConfigProxy <T> proxy = null;

            proxy = new ConfigProxy <T>(readValue, null, validKVPFound, tryUpdateValueInConfigFile, tryUpdateCommentaryInConfigFile, tryRemoveValueFromConfigFile);
            return(proxy);

            ////////////////////////////////////////////////////

            void tryAppendKVP()
            {
                if (!_config.KVPs.IsReadOnly)
                {
                    pack(fallbackValue);
                }
            }

            void tryUpdateValueInConfigFile(T newValue)
            {
                if (!_config.KVPs.IsReadOnly)
                {
                    pack(newValue);
                }
            }

            void pack(T newValue)
            {
                var accessorProxy = getProxy();
                var isOk          = marshaller.TryPack(accessorProxy, newValue);

                if (!isOk)
                {
                    accessorProxy.Clear();
                }
            }

            void tryUpdateCommentaryInConfigFile(string newValue)
            {
                // No notion for objects' commentaries
            }

            void tryRemoveValueFromConfigFile()
            {
                if (!_config.KVPs.IsReadOnly)
                {
                    _config.KVPs.Remove(kvp => kvp.Section.Equals(section));
                    _proxyPaths.Remove(path);
                }
            }

            ConfigAccessorProxy getProxy()
            {
                return(new ConfigAccessorProxy(this, section.GetSubsection(_config.RootSection)));
            }
        }
        public ConfigProxy <T> ReadValueFrom <T>(T fallbackValue, string subsection, [CallerMemberName] string key = "")
        {
            var path = $".{subsection}.{key}";

            if (_proxyPaths.Contains(path))
            {
                throw new InvalidOperationException("Значение с данным ключем уже было прочитано");
            }

            var valueType = typeof(T).IsArray
                ? typeof(T).GetElementType()
                : typeof(T);
            var marshaller = GetValueMarshaller(valueType);
            var section    = new Section(_config.RootSection, subsection);

            if (marshaller == null)
            {
                throw new NotSupportedException($"Тип {typeof(T)} не поддерживается.");
            }
            else if (!isKeyCorrect() || !section.IsCorrect)
            {
                throw new ArgumentException();
            }

            T         readValue      = fallbackValue;
            string    readCommentary = null;
            ConfigKVP kvpReference   = null;
            bool      validKVPFound  = false;

            foreach (var kvp in _config.KVPs)
            {
                if (kvp.Key == key && kvp.Section.Equals(section))
                {
                    var isParsed = marshaller.TryUnpack(kvp.Value, valueType, out dynamic parsedValue);
                    readValue      = isParsed ? cast(parsedValue) : fallbackValue;
                    readCommentary = kvp.Commentary;
                    validKVPFound  = isParsed;
                    if (validKVPFound)
                    {
                        kvpReference = kvp;
                        break;
                    }
                }
            }
            if (!validKVPFound)
            {
                tryAppendKVP();
            }

            _proxyPaths.Add(path);
            ConfigProxy <T> proxy = null;

            proxy = new ConfigProxy <T>(readValue, readCommentary, validKVPFound, tryUpdateValueInConfigFile, tryUpdateCommentaryInConfigFile, tryRemoveValueFromConfigFile);
            return(proxy);

            ////////////////////////////////////////////////////

            void tryAppendKVP()
            {
                if (!_config.KVPs.IsReadOnly)
                {
                    kvpReference = pack(fallbackValue, null);
                    _config.KVPs.Add(kvpReference);
                }
            }

            ConfigKVP pack(T value, string commentary)
            {
                var isOk = marshaller.TryPack(value, out ConfigValue packedValue);

                if (isOk)
                {
                    commentary = commentary?.Replace(Global.NL, "");
                    return(new ConfigKVP(section, key, packedValue, commentary));
                }
                else
                {
                    throw null;
                }
            }

            bool isKeyCorrect()
            {
                return(key?.All(c => char.IsLetterOrDigit(c) || c == '_') ?? false);
            }

            void tryUpdateValueInConfigFile(T newValue)
            {
                var kvpIndex = _config.KVPs.Find(kvpReference).Index;

                if (!_config.KVPs.IsReadOnly && kvpIndex != -1)
                {
                    kvpReference = _config.KVPs[kvpIndex] = pack(newValue, null);
                }
            }

            void tryUpdateCommentaryInConfigFile(string newValue)
            {
                var kvpIndex = _config.KVPs.Find(kvpReference).Index;

                if (!_config.KVPs.IsReadOnly && kvpIndex != -1)
                {
                    kvpReference = _config.KVPs[kvpIndex] = pack(proxy.Value, newValue);
                }
            }

            void tryRemoveValueFromConfigFile()
            {
                var kvpIndex = _config.KVPs.Find(kvpReference).Index;

                if (!_config.KVPs.IsReadOnly && kvpIndex != -1)
                {
                    _config.KVPs.RemoveAt(kvpIndex);
                    _proxyPaths.Remove(path);
                }
            }

            T cast(dynamic value)
            {
                if (value == null)
                {
                    return(value);
                }

                var valueT  = value.GetType();
                var castToT = typeof(T);

                if (castToT.IsArray)
                {
                    return(castAsArrayElement());
                }
                else
                {
                    return(castAsSingleElement());
                }

                ///////////////////////////

                T castAsArrayElement()
                {
                    var isEmptyArray = ((Array)value).Length == 0;
                    var isArrayOfT   = valueT.IsArray
                        ? (isEmptyArray ? false : value[0].GetType() == castToT.GetElementType())
                        : false;

                    if (isArrayOfT)
                    {
                        var     arr    = (Array)value;
                        dynamic result = Array.CreateInstance(castToT.GetElementType(), arr.Length);
                        var     i      = 0;
                        foreach (var item in arr)
                        {
                            result.SetValue(item, i);
                            i++;
                        }

                        return(result);
                    }
                    else if (isEmptyArray)
                    {
                        dynamic result = Array.CreateInstance(castToT.GetElementType(), 0);

                        return(result);
                    }
                    else
                    {
                        throw new ArgumentException();
                    }
                }

                T castAsSingleElement()
                {
                    var isArrayOfT = valueT.IsArray
                        ? (((Array)value).Length > 0 ? value?[0]?.GetType() == castToT : false)
                                     : false;

                    if (isArrayOfT || (valueT.IsArray && value?[0] == null))
                    {
                        var arr = (Array)value;
                        if (arr.Length == 1)
                        {
                            return((T)value[0]);
                        }
                        else
                        {
                            throw new ArgumentException();
                        }
                    }
                    else if (valueT == castToT)
                    {
                        return((T)value);
                    }
                    else
                    {
                        throw new ArgumentException();
                    }
                }
            }
        }