Skip to content

Commit

Permalink
ObjectReflectionCache - Added support for IReadOnlyDictionary as expa…
Browse files Browse the repository at this point in the history
…ndo object
  • Loading branch information
snakefoot committed Apr 5, 2020
1 parent 4d13880 commit 68ea14c
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 9 deletions.
45 changes: 36 additions & 9 deletions src/NLog/Internal/ObjectReflectionCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,24 @@ private static bool TryExtractExpandoObject(Type objectType, out ObjectPropertyI
{
foreach (var interfaceType in objectType.GetInterfaces())
{
if (interfaceType.IsGenericType() && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
if (interfaceType.IsGenericType())
{
if (interfaceType.GetGenericArguments()[0] == typeof(string))
{
var dictionaryEnumerator = (IDictionaryEnumerator)Activator.CreateInstance(typeof(DictionaryEnumerator<,>).MakeGenericType(interfaceType.GetGenericArguments()));
propertyInfos = new ObjectPropertyInfos(null, new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => dictionaryEnumerator.GetEnumerator(o)) });
return true;
if (interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
var dictionaryEnumerator = (IDictionaryEnumerator)Activator.CreateInstance(typeof(DictionaryEnumerator<,>).MakeGenericType(interfaceType.GetGenericArguments()));
propertyInfos = new ObjectPropertyInfos(null, new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => dictionaryEnumerator.GetEnumerator(o)) });
return true;
}
#if NET4_5
if (interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))
{
var dictionaryEnumerator = (IDictionaryEnumerator)Activator.CreateInstance(typeof(DictionaryEnumerator<,>).MakeGenericType(interfaceType.GetGenericArguments()));
propertyInfos = new ObjectPropertyInfos(null, new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => dictionaryEnumerator.GetEnumerator(o)) });
return true;
}
#endif
}
}
}
Expand Down Expand Up @@ -239,7 +250,7 @@ private static FastPropertyLookup[] BuildFastLookup(PropertyInfo[] properties, b
{
var getterMethod = prop.GetGetMethod();
Type propertyType = getterMethod.ReturnType;
ReflectionHelpers.LateBoundMethod valueLookup = ReflectionHelpers.CreateLateBoundMethod(getterMethod);
ReflectionHelpers.LateBoundMethodSingle valueLookup = ReflectionHelpers.CreateLateBoundMethodSingle(getterMethod);
#if NETSTANDARD1_3
TypeCode typeCode = propertyType == typeof(string) ? TypeCode.String : (propertyType == typeof(int) ? TypeCode.Int32 : TypeCode.Object);
#else
Expand Down Expand Up @@ -477,11 +488,11 @@ public void Reset()
internal struct FastPropertyLookup
{
public readonly string Name;
public readonly ReflectionHelpers.LateBoundMethod ValueLookup;
public readonly ReflectionHelpers.LateBoundMethodSingle ValueLookup;
public readonly TypeCode TypeCode;
public readonly int NameHashCode;

public FastPropertyLookup(string name, TypeCode typeCode, ReflectionHelpers.LateBoundMethod valueLookup)
public FastPropertyLookup(string name, TypeCode typeCode, ReflectionHelpers.LateBoundMethodSingle valueLookup)
{
Name = name;
ValueLookup = valueLookup;
Expand Down Expand Up @@ -549,16 +560,24 @@ private interface IDictionaryEnumerator
IEnumerator<KeyValuePair<string,object>> GetEnumerator(object value);
}

private static readonly IDictionary<string, object> EmptyDictionary = new NLog.Internal.SortHelpers.ReadOnlySingleBucketDictionary<string, object>();
private static readonly IDictionary<string, object> EmptyDictionary = new SortHelpers.ReadOnlySingleBucketDictionary<string, object>();

internal sealed class DictionaryEnumerator<TKey, TValue> : IDictionaryEnumerator
{
public IEnumerator<KeyValuePair<string, object>> GetEnumerator(object value)
{
if (value is IDictionary<TKey, TValue> dictionary)
{
return YieldEnumerator(dictionary);
if (dictionary.Count > 0)
return YieldEnumerator(dictionary);
}
#if NET4_5
else if (value is IReadOnlyDictionary<TKey, TValue> readonlyDictionary)
{
if (readonlyDictionary.Count > 0)
return YieldEnumerator(readonlyDictionary);
}
#endif
return EmptyDictionary.GetEnumerator();
}

Expand All @@ -567,6 +586,14 @@ private IEnumerator<KeyValuePair<string, object>> YieldEnumerator(IDictionary<TK
foreach (var item in dictionary)
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
}

#if NET4_5
private IEnumerator<KeyValuePair<string, object>> YieldEnumerator(IReadOnlyDictionary<TKey, TValue> dictionary)
{
foreach (var item in dictionary)
yield return new KeyValuePair<string, object>(item.Key.ToString(), item.Value);
}
#endif
}
}
}
80 changes: 80 additions & 0 deletions tests/NLog.UnitTests/Internal/ReadOnlyExpandoTestDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// Copyright (c) 2004-2020 Jaroslaw Kowalski <[email protected]>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//

#if NET4_5

namespace NLog.UnitTests.Internal
{
using System.Collections;
using System.Collections.Generic;

class ReadOnlyExpandoTestDictionary : IReadOnlyDictionary<string, object>
{
private readonly IReadOnlyDictionary<string, object> _internal;

public ReadOnlyExpandoTestDictionary(IReadOnlyDictionary<string, object> dictionary)
{
_internal = dictionary;
}

public object this[string key] => _internal[key];

public IEnumerable<string> Keys => _internal.Keys;

public IEnumerable<object> Values => _internal.Values;

public int Count => _internal.Count;

public bool ContainsKey(string key)
{
return _internal.ContainsKey(key);
}

public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return _internal.GetEnumerator();
}

public bool TryGetValue(string key, out object value)
{
return _internal.TryGetValue(key, out value);
}

IEnumerator IEnumerable.GetEnumerator()
{
return _internal.GetEnumerator();
}
}
}

#endif
14 changes: 14 additions & 0 deletions tests/NLog.UnitTests/Targets/DefaultJsonSerializerTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,20 @@ public void SerializeExpandoDict_Test()
Assert.Equal("{\"key 2\":1.3, \"level\":{\"Name\":\"Info\", \"Ordinal\":2}}", actual);
}

#if NET4_5
[Fact]
public void SerializeReadOnlyExpandoDict_Test()
{
var dictionary = new Dictionary<string, object>();
dictionary.Add("key 2", 1.3m);
dictionary.Add("level", LogLevel.Info);

var readonlyDictionary = new Internal.ReadOnlyExpandoTestDictionary(dictionary);
var actual = SerializeObject(readonlyDictionary);
Assert.Equal("{\"key 2\":1.3, \"level\":{\"Name\":\"Info\", \"Ordinal\":2}}", actual);
}
#endif

[Fact]
public void SerializeIntegerKeyDict_Test()
{
Expand Down

0 comments on commit 68ea14c

Please sign in to comment.