Skip to content

Commit

Permalink
feat: Support of dictionary indexers to get values by keys instead of…
Browse files Browse the repository at this point in the history
… KeyValuePair instances.
  • Loading branch information
hennadiilu authored May 7, 2024
1 parent 240d182 commit 8484567
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 30 deletions.
20 changes: 13 additions & 7 deletions src/Heleonix.Reflection/Reflector.Get.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
namespace Heleonix.Reflection
{
using System;
using System.Globalization;
using System.Reflection;

/// <summary>
Expand Down Expand Up @@ -85,16 +84,16 @@ public static bool Get<TReturn>(
var size = (dot == -1) ? memberPath.Length : dot;
var prop = memberPath.Substring(0, size);

var index = -1;
object index = null;
var indexerEnd = prop.LastIndexOf(']');

if (indexerEnd != -1)
{
var indexerStart = prop.IndexOf('[');

index = int.Parse(
prop.Substring(indexerStart + 1, indexerEnd - indexerStart - 1),
CultureInfo.CurrentCulture);
var indexValue = prop.Substring(indexerStart + 1, indexerEnd - indexerStart - 1);

index = int.TryParse(indexValue, out int result) ? result : indexValue;

prop = prop.Substring(0, indexerStart);
}
Expand All @@ -112,9 +111,16 @@ public static bool Get<TReturn>(
return false;
}

if (index != -1)
if (index != null)
{
container = GetElementAt(container, index);
var isElementFound = GetElementAt(container, index, out container);

if (!isElementFound)
{
value = default;

return false;
}

if (container == null)
{
Expand Down
21 changes: 10 additions & 11 deletions src/Heleonix.Reflection/Reflector.Set.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace Heleonix.Reflection
using System;
using System.Collections;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;

/// <summary>
Expand Down Expand Up @@ -188,7 +187,7 @@ private static bool Set(

var container = instance;
var containerType = container?.GetType() ?? type;
int index;
object index;
MemberInfo memberInfo;

while (true)
Expand All @@ -199,15 +198,15 @@ private static bool Set(

var indexerEnd = prop.LastIndexOf(']');

index = -1;
index = null;

if (indexerEnd != -1)
{
var indexerStart = prop.IndexOf('[');

index = int.Parse(
prop.Substring(indexerStart + 1, indexerEnd - indexerStart - 1),
CultureInfo.CurrentCulture);
var indexValue = prop.Substring(indexerStart + 1, indexerEnd - indexerStart - 1);

index = int.TryParse(indexValue, out int result) ? result : indexValue;

prop = prop.Substring(0, indexerStart);
}
Expand All @@ -216,7 +215,7 @@ private static bool Set(
.GetTypeInfo()
.GetMember(prop, MemberTypes.Field | MemberTypes.Property, bindingFlags);

if (dot == -1 && index == -1)
if (dot == -1 && index == null)
{
memberInfo = memberInfos.Length > 0 ? memberInfos[0] : null;

Expand All @@ -230,13 +229,13 @@ private static bool Set(
return false;
}

if (index != -1)
if (index != null)
{
if (dot != -1)
{
container = GetElementAt(container, index);
var isElementFound = GetElementAt(container, index, out container);

if (container == null)
if (!isElementFound || container == null)
{
return false;
}
Expand Down Expand Up @@ -266,7 +265,7 @@ private static bool Set(
value = CoerceValue(pi.PropertyType, value);
}

pi.SetValue(container, value, index != -1 ? new object[] { index } : null);
pi.SetValue(container, value, index != null ? new object[] { index } : null);

return true;
}
Expand Down
50 changes: 38 additions & 12 deletions src/Heleonix.Reflection/Reflector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ public const BindingFlags DefaultBindingFlags
/// </summary>
private const MemberTypes PropertyOrFieldMemberTypes = MemberTypes.Property | MemberTypes.Field;

#pragma warning disable CA1825 // Avoid zero-length array allocations.
/// <summary>
/// The empty member information.
/// </summary>
private static readonly MemberInfo[] EmptyMemberInfo = new MemberInfo[0];
#pragma warning restore CA1825 // Avoid zero-length array allocations.
private static readonly MemberInfo[] EmptyMemberInfo = Array.Empty<MemberInfo>();

/// <summary>
/// Determines whether the specified property is static by its getter (if it is defined) or by its setter (if it is defined).
Expand Down Expand Up @@ -74,17 +72,41 @@ private static bool ParameterTypesMatch(ParameterInfo[] paramInfos, Type[] param
/// </summary>
/// <param name="container">A container to get an element from.</param>
/// <param name="index">An index to get an elementa at.</param>
/// <returns>An element by the specified <paramref name="index"/>.</returns>
private static object GetElementAt(object container, int index)
/// <param name="element">The elemebt retrieved by the specified <paramref name="index"/> or null.</param>
/// <returns><c>true</c>, if an element is found by the specified <paramref name="index"/>, otherwise <c>false</c>.</returns>
private static bool GetElementAt(object container, object index, out object element)
{
if (container is IDictionary dictionary)
{
foreach (var key in dictionary.Keys)
{
if (key.Equals(index) || Convert.ToString(key) == Convert.ToString(index))
{
element = dictionary[key];

return true;
}
}

element = null;

return false;
}

var intIndex = (int)index;

if (container is IList list)
{
if (index >= list.Count)
if (intIndex >= list.Count)
{
return null;
element = null;

return false;
}

return list[index];
element = list[intIndex];

return true;
}

if (container is IEnumerable enumerable)
Expand All @@ -93,16 +115,20 @@ private static object GetElementAt(object container, int index)

while (enumerator.MoveNext())
{
if (index == 0)
if (intIndex == 0)
{
return enumerator.Current;
element = enumerator.Current;

return true;
}

index--;
intIndex--;
}
}

return null;
element = null;

return false;
}

/// <summary>
Expand Down
10 changes: 10 additions & 0 deletions test/Heleonix.Reflection.Tests/Common/Dummies/SubItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,15 @@ public class SubItem
/// Gets or sets the sub sub items enumerable property.
/// </summary>
public IEnumerable<SubSubItem> SubSubItemsEnumerableProperty { get; set; } = new Queue<SubSubItem>();

/// <summary>
/// Gets or sets the sub sub items string dictionary property.
/// </summary>
public Dictionary<string, SubSubItem> SubSubItemsStringDictionaryProperty { get; set; } = new Dictionary<string, SubSubItem>();

/// <summary>
/// Gets or sets the sub sub items int dictionary property.
/// </summary>
public Dictionary<int, SubSubItem> SubSubItemsIntDictionaryProperty { get; set; } = new Dictionary<int, SubSubItem>();
}
}
73 changes: 73 additions & 0 deletions test/Heleonix.Reflection.Tests/Reflector.Get.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,71 @@ public static void Get()
Assert.That(returnValue, Is.True);
});
});

And("the index is out of range", () =>
{
memberPath = "ItemsProperty[11111]";

Should("provide default value and return false", () =>
{
Assert.That(result, Is.Null);
Assert.That(returnValue, Is.False);
});
});
});

And("the indexer is in a middle of the memberPath", () =>
{
And("the collection is a string-keyed dictionary", () =>
{
instance.SubItemProperty.SubSubItemsStringDictionaryProperty.Add(
"First Key",
new SubSubItem { TextProperty = "First Value" });
memberPath = "SubItemProperty.SubSubItemsStringDictionaryProperty[First Key].TextProperty";

Should("provide the target value", () =>
{
Assert.That(result, Is.EqualTo("First Value"));
Assert.That(returnValue, Is.True);
});

And("the key does not exist", () =>
{
memberPath = "SubItemProperty.SubSubItemsStringDictionaryProperty[NO KEY].TextProperty";

Should("provide the default value and return false", () =>
{
Assert.That(result, Is.Null);
Assert.That(returnValue, Is.False);
});
});
});

And("the collection is an int-keyed dictionary", () =>
{
instance.SubItemProperty.SubSubItemsIntDictionaryProperty.Add(
12345,
new SubSubItem { TextProperty = "First Value" });
memberPath = "SubItemProperty.SubSubItemsIntDictionaryProperty[12345].TextProperty";

Should("provide the target value", () =>
{
Assert.That(result, Is.EqualTo("First Value"));
Assert.That(returnValue, Is.True);
});

And("the key does not exist", () =>
{
memberPath = "SubItemProperty.SubSubItemsStringDictionaryProperty[111].TextProperty";

Should("provide the default value and return false", () =>
{
Assert.That(result, Is.Null);
Assert.That(returnValue, Is.False);
});
});
});

And("the collection is a list", () =>
{
instance.SubItemProperty.SubSubItemsListProperty.AddRange(
Expand All @@ -220,6 +281,18 @@ public static void Get()
Assert.That(returnValue, Is.False);
});
});

And("the item by the specified index is null", () =>
{
instance.SubItemProperty.SubSubItemsListProperty[0] = null;
memberPath = "SubItemProperty.SubSubItemsListProperty[0].TextProperty";

Should("provide default value and return false", () =>
{
Assert.That(result, Is.Null);
Assert.That(returnValue, Is.False);
});
});
});

And("the collection is not a list", () =>
Expand Down
27 changes: 27 additions & 0 deletions test/Heleonix.Reflection.Tests/Reflector.Set.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,33 @@ public static void Set()

And("the indexer is in a middle of the memberPath", () =>
{
And("the collection is a string-keyed dictionary", () =>
{
instance.SubItemProperty.SubSubItemsStringDictionaryProperty.Add(
"First Key",
new SubSubItem { TextProperty = "First Value" });
memberPath = "SubItemProperty.SubSubItemsStringDictionaryProperty[First Key].TextProperty";
value = "New First Value";

Should("set the value and return true", () =>
{
Assert.That(
instance.SubItemProperty.SubSubItemsStringDictionaryProperty["First Key"].TextProperty,
Is.EqualTo(value));
Assert.That(returnValue, Is.True);
});

And("the key does not exist", () =>
{
memberPath = "SubItemProperty.SubSubItemsStringDictionaryProperty[NO KEY].TextProperty";

Should("return false", () =>
{
Assert.That(returnValue, Is.False);
});
});
});

And("the collection is a list", () =>
{
instance.SubItemProperty.SubSubItemsListProperty.AddRange(
Expand Down

0 comments on commit 8484567

Please sign in to comment.