bool TryConvertEnumerable(object value, Destructuring destructuring, Type valueType, out LogEventPropertyValue result)
        {
            if (value is IEnumerable enumerable)
            {
                // Only dictionaries with 'scalar' keys are permitted, as
                // more complex keys may not serialize to unique values for
                // representation in sinks. This check strengthens the expectation
                // that resulting dictionary is representable in JSON as well
                // as richer formats (e.g. XML, .NET type-aware...).
                // Only actual dictionaries are supported, as arbitrary types
                // can implement multiple IDictionary interfaces and thus introduce
                // multiple different interpretations.
                if (TryGetDictionary(value, valueType, out var dictionary))
                {
                    result = new DictionaryValue(MapToDictionaryElements(dictionary, destructuring));
                    return true;

                    IEnumerable<KeyValuePair<ScalarValue, LogEventPropertyValue>> MapToDictionaryElements(IDictionary dictionaryEntries, Destructuring destructure)
                    {
                        var count = 0;
                        foreach (DictionaryEntry entry in dictionaryEntries)
                        {
                            if (++count > _maximumCollectionCount)
                            {
                                yield break;
                            }

                            var pair = new KeyValuePair<ScalarValue, LogEventPropertyValue>(
                                (ScalarValue)_depthLimiter.CreatePropertyValue(entry.Key, destructure),
                                _depthLimiter.CreatePropertyValue(entry.Value, destructure));

                            if (pair.Key.Value != null)
                                yield return pair;
                        }
                    }
                }

                result = new SequenceValue(MapToSequenceElements(enumerable, destructuring));
                return true;

                IEnumerable<LogEventPropertyValue> MapToSequenceElements(IEnumerable sequence, Destructuring destructure)
                {
                    var count = 0;
                    foreach (var element in sequence)
                    {
                        if (++count > _maximumCollectionCount)
                        {
                            yield break;
                        }

                        yield return _depthLimiter.CreatePropertyValue(element, destructure);
                    }
                }
            }

            result = null;
            return false;
        }
        TemplatePropertyValue CreatePropertyValue(object value, Destructuring destructuring, int depth)
        {
            if (value == null)
            {
                return(new ScalarValue(null));
            }

            if (destructuring == Destructuring.Stringify)
            {
                return(new ScalarValue(value.ToString()));
            }

            var valueType = value.GetType();
            var limiter   = new DepthLimiter(depth, _maximumDestructuringDepth, this);

            foreach (var scalarConversionPolicy in _scalarConversionPolicies)
            {
                ScalarValue converted;
                if (scalarConversionPolicy.TryConvertToScalar(value, limiter, out converted))
                {
                    return(converted);
                }
            }

            if (destructuring == Destructuring.Destructure)
            {
                foreach (var destructuringPolicy in _destructuringPolicies)
                {
                    TemplatePropertyValue result;
                    if (destructuringPolicy.TryDestructure(value, limiter, out result))
                    {
                        return(result);
                    }
                }
            }

            var enumerable = value as IEnumerable;

            if (enumerable != null)
            {
                // Only dictionaries with 'scalar' keys are permitted, as
                // more complex keys may not serialize to unique values for
                // representation in sinks. This check strengthens the expectation
                // that resulting dictionary is representable in JSON as well
                // as richer formats (e.g. XML, .NET type-aware...).
                // Only actual dictionaries are supported, as arbitrary types
                // can implement multiple IDictionary interfaces and thus introduce
                // multiple different interpretations.
                if (IsValueTypeDictionary(valueType))
                {
                    Func <object, object> getKey;
                    Func <object, object> getValue;
#if !REFLECTION_API_EVOLVED // https://blogs.msdn.microsoft.com/dotnet/2012/08/28/evolving-the-reflection-api/
                    PropertyInfo keyProperty = null;
                    getKey = o => (keyProperty ?? (keyProperty = o.GetType().GetProperty("Key"))).GetValue(o, null);
                    PropertyInfo valueProperty = null;
                    getValue = o => (valueProperty ?? (valueProperty = o.GetType().GetProperty("Value"))).GetValue(o, null);
#else
                    PropertyInfo keyProperty = null;
                    getKey = o => (keyProperty ?? (keyProperty = o.GetType().GetRuntimeProperty("Key"))).GetValue(o);
                    PropertyInfo valueProperty = null;
                    getValue = o => (valueProperty ?? (valueProperty = o.GetType().GetRuntimeProperty("Value"))).GetValue(o);
#endif

                    // TODO: stop using LINQ
                    return(new DictionaryValue(enumerable
                                               .Cast <object>()
                                               .Where(o => o != null)
                                               .Select(o => new { Key = getKey(o), Value = getValue(o) })
                                               .Select(o => new KeyValuePair <ScalarValue, TemplatePropertyValue>(
                                                           key: (ScalarValue)limiter.CreatePropertyValue(o.Key, destructuring),
                                                           value: limiter.CreatePropertyValue(o.Value, destructuring))
                                                       )
                                               ));
                }

                return(new SequenceValue(
                           enumerable.Cast <object>().Select(o => limiter.CreatePropertyValue(o, destructuring))));
            }

            if (destructuring == Destructuring.Destructure)
            {
                var type    = value.GetType();
                var typeTag = type.Name;
#if REFLECTION_API_EVOLVED
                if (typeTag.Length <= 0 || IsCompilerGeneratedType(type))
                {
                    typeTag = null;
                }
#else
                if (typeTag.Length <= 0 || !char.IsLetter(typeTag[0]))
                {
                    typeTag = null;
                }
#endif
                return(new StructureValue(GetProperties(value, limiter), typeTag));
            }

            return(new ScalarValue(value.ToString()));
        }
        TemplatePropertyValue CreatePropertyValue(object value, Destructuring destructuring, int depth)
        {
            if (value == null)
                return new ScalarValue(null);

            if (destructuring == Destructuring.Stringify)
                return new ScalarValue(value.ToString());

            var valueType = value.GetType();
            var limiter = new DepthLimiter(depth, _maximumDestructuringDepth, this);

            foreach (var scalarConversionPolicy in _scalarConversionPolicies)
            {
                ScalarValue converted;
                if (scalarConversionPolicy.TryConvertToScalar(value, limiter, out converted))
                    return converted;
            }

            if (destructuring == Destructuring.Destructure)
            {
                foreach (var destructuringPolicy in _destructuringPolicies)
                {
                    TemplatePropertyValue result;
                    if (destructuringPolicy.TryDestructure(value, limiter, out result))
                        return result;
                }
            }

            #region IEnumerable->SequenceValue|DictionaryValue

            var enumerable = value as IEnumerable;
            if (enumerable != null)
            {
                // Only dictionaries with 'scalar' keys are permitted, as
                // more complex keys may not serialize to unique values for
                // representation in sinks. This check strengthens the expectation
                // that resulting dictionary is representable in JSON as well
                // as richer formats (e.g. XML, .NET type-aware...).
                // Only actual dictionaries are supported, as arbitrary types
                // can implement multiple IDictionary interfaces and thus introduce
                // multiple different interpretations.
                if (IsValueTypeDictionary(valueType))
                {
                    Func<object, object> getKey;
                    Func<object, object> getValue;
            #if USE_REFLECTION_40
                    PropertyInfo keyProperty = null;
                    getKey = o => (keyProperty ?? (keyProperty = o.GetType().GetProperty("Key"))).GetValue(o, null);
                    PropertyInfo valueProperty = null;
                    getValue = o => (valueProperty ?? (valueProperty = o.GetType().GetProperty("Value"))).GetValue(o, null);
            #else
                    PropertyInfo keyProperty = null;
                    getKey = o => (keyProperty ?? (keyProperty = o.GetType().GetRuntimeProperty("Key"))).GetValue(o);
                    PropertyInfo valueProperty = null;
                    getValue = o => (valueProperty ?? (valueProperty = o.GetType().GetRuntimeProperty("Value"))).GetValue(o);
            #endif

                    return new DictionaryValue(enumerable
                        .Cast<object>()
                        .Where(o => o != null)
                        .Select(o => new { Key = getKey(o), Value = getValue(o) })
                        .Select(o => new KeyValuePair<ScalarValue, TemplatePropertyValue>(
                            key: (ScalarValue)limiter.CreatePropertyValue(o.Key, destructuring),
                            value: limiter.CreatePropertyValue(o.Value, destructuring))
                        )
                    );
                }

                return new SequenceValue(
                    enumerable.Cast<object>().Select(o => limiter.CreatePropertyValue(o, destructuring)));
            }

            #endregion

            if (destructuring == Destructuring.Destructure)
            {
                var typeTag = value.GetType().Name;
                if (typeTag.Length <= 0 || !char.IsLetter(typeTag[0]))
                    typeTag = null;

                return new StructureValue(GetProperties(value, limiter), typeTag);
            }

            return new ScalarValue(value.ToString());
        }