Skip to content

Commit afd6c87

Browse files
committed
Merge branch 'improvement/ItemMetadata' into develop
Established `ItemMetadata` (bec7123) as a way of unifying `MemberAccessor` and the new `ParameterMetadata` class (37eeb3c) so that support for mapping constructors can be further unified with support for mapping properties in the `TopicMappingService`. This exposes the new `ParameterMetadata` class (37eeb3c) as a cached collection associated with the primary constructor on `TypeAccessor` (863b618), helping make constructor support a first-class feature. Further integrated `ParameterMetadata` into `TopicMappingService` (3659828) and `ItemConfiguration` (63d32a2). With this, I established `IsList` and `IsConvertible` as cached properties on `ItemMetadata` so they don't need to be dynamically evaluated for each instance a property (d0fb8a4, df86a0e). While I was at it, I made various other improvements to the `TopicMappingService`, such as caching the `TypeAccessor` for `Topic` (0539c8d), limiting the scope of reference lookups (83d5c3d), optimizing the order of `GetValue()` (8f80333), and some performance optimizations of `AsAttributeDictionary()` (5b68add).
2 parents ac2b534 + 5b68add commit afd6c87

10 files changed

Lines changed: 287 additions & 143 deletions

File tree

OnTopic.Tests/AttributeDictionaryTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,9 @@ public void AsAttributeDictionary_ExcludedKeys_Excluded() {
212212
public void AsAttributeDictionary_InheritFromBase_InheritsValues() {
213213

214214
var baseTopic = new Topic("BaseTopic", "Page");
215-
var topic = new Topic("Test", "Page");
216-
217-
topic.BaseTopic = baseTopic;
215+
var topic = new Topic("Test", "Page") {
216+
BaseTopic = baseTopic
217+
};
218218

219219
baseTopic.Attributes.SetValue("Subtitle", "Subtitle");
220220

OnTopic.Tests/MemberAccessorTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class MemberAccessorTest {
2929
\-------------------------------------------------------------------------------------------------------------------------*/
3030
/// <summary>
3131
/// Assembles a new <see cref="MemberAccessor"/> from a <see cref="MemberInfo"/> that is nullable, and confirms that the
32-
/// <see cref="MemberAccessor.IsNullable"/> property is set to <c>true</c>.
32+
/// <see cref="ItemMetadata.IsNullable"/> property is set to <c>true</c>.
3333
/// </summary>
3434
[Fact]
3535
public void IsNullable_NullableProperty_ReturnsTrue() {
@@ -47,7 +47,7 @@ public void IsNullable_NullableProperty_ReturnsTrue() {
4747
\-------------------------------------------------------------------------------------------------------------------------*/
4848
/// <summary>
4949
/// Assembles a new <see cref="MemberAccessor"/> from a <see cref="MemberInfo"/> that is not nullable, and confirms that
50-
/// the <see cref="MemberAccessor.IsNullable"/> property is set to <c>false</c>.
50+
/// the <see cref="ItemMetadata.IsNullable"/> property is set to <c>false</c>.
5151
/// </summary>
5252
[Fact]
5353
public void IsNullable_NonNullableProperty_ReturnsFalse() {
@@ -65,7 +65,7 @@ public void IsNullable_NonNullableProperty_ReturnsFalse() {
6565
\-------------------------------------------------------------------------------------------------------------------------*/
6666
/// <summary>
6767
/// Assembles a new <see cref="MemberAccessor"/> from a <see cref="MemberInfo"/> that is a non-nullable reference type,
68-
/// and confirms that the <see cref="MemberAccessor.IsNullable"/> property is set to <c>true</c>.
68+
/// and confirms that the <see cref="ItemMetadata.IsNullable"/> property is set to <c>true</c>.
6969
/// </summary>
7070
/// <remarks>
7171
/// Unfortunately, the .NET reflection libraries don't (yet) have the ability to determine if a nullable reference types

OnTopic/Attributes/AttributeCollection.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,19 @@ public void SetValue(
159159
/// <returns>A new <see cref="AttributeDictionary"/> containing attributes</returns>
160160
public AttributeDictionary AsAttributeDictionary(bool inheritFromBase = false) {
161161
var sourceAttributes = (AttributeCollection?)this;
162-
var attributes = new AttributeDictionary();
163-
while (sourceAttributes is not null) {
162+
var attributes = new AttributeDictionary();
163+
var count = 0;
164+
while (sourceAttributes is not null && ++count < 5) {
164165
foreach (var attribute in sourceAttributes) {
165-
if (!_excludedAttributes.Contains(attribute.Key) && !attributes.ContainsKey(attribute.Key)) {
166-
attributes.Add(attribute.Key, attribute.Value);
166+
if (count is 1 || !attributes.ContainsKey(attribute.Key)) {
167+
attributes.TryAdd(attribute.Key, attribute.Value);
167168
}
168169
}
169170
sourceAttributes = inheritFromBase? sourceAttributes.AssociatedTopic.BaseTopic?.Attributes : null;
170171
}
172+
foreach (var attribute in _excludedAttributes) {
173+
attributes.Remove(attribute);
174+
}
171175
return attributes;
172176
}
173177

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*==============================================================================================================================
2+
| Author Ignia, LLC
3+
| Client Ignia, LLC
4+
| Project Topics Library
5+
\=============================================================================================================================*/
6+
using System.Collections;
7+
using System.Reflection;
8+
using OnTopic.Attributes;
9+
10+
namespace OnTopic.Internal.Reflection {
11+
12+
/*============================================================================================================================
13+
| CLASS: ITEM METADATA
14+
\---------------------------------------------------------------------------------------------------------------------------*/
15+
/// <summary>
16+
/// Provides metadata associated with a given parameter, method, or property.
17+
/// </summary>
18+
internal abstract class ItemMetadata {
19+
20+
/*==========================================================================================================================
21+
| PRIVATE VARIABLES
22+
\-------------------------------------------------------------------------------------------------------------------------*/
23+
private readonly static List<Type> _listTypes = new();
24+
private readonly ICustomAttributeProvider _attributeProvider;
25+
private readonly Type _type = default!;
26+
private List<Attribute> _customAttributes = default!;
27+
28+
/*==========================================================================================================================
29+
| CONSTRUCTOR
30+
\-------------------------------------------------------------------------------------------------------------------------*/
31+
/// <summary>
32+
/// Establishes a new instance of a <see cref="ItemMetadata"/> with required dependencies.
33+
/// </summary>
34+
static ItemMetadata() {
35+
_listTypes.Add(typeof(IEnumerable<>));
36+
_listTypes.Add(typeof(ICollection<>));
37+
_listTypes.Add(typeof(IList<>));
38+
}
39+
40+
/*==========================================================================================================================
41+
| CONSTRUCTOR
42+
\-------------------------------------------------------------------------------------------------------------------------*/
43+
/// <summary>
44+
/// Initializes a new instance of the <see cref="ItemMetadata"/> class associated with a <see cref="MemberInfo"/> or <see
45+
/// cref="ParameterInfo"/> instance.
46+
/// </summary>
47+
/// <param name="name">The <see cref="Name"/> of the <see cref="MemberInfo"/> or <see cref="ParameterInfo"/>.</param>
48+
/// <param name="attributeProvider">
49+
/// The <see cref="MemberInfo"/> or <see cref="ParameterInfo"/> associated with the <see cref="ItemMetadata"/>.
50+
/// </param>
51+
internal ItemMetadata(string name, ICustomAttributeProvider attributeProvider) {
52+
53+
/*------------------------------------------------------------------------------------------------------------------------
54+
| Set Fields
55+
\-----------------------------------------------------------------------------------------------------------------------*/
56+
_attributeProvider = attributeProvider;
57+
58+
/*------------------------------------------------------------------------------------------------------------------------
59+
| Set Properties
60+
\-----------------------------------------------------------------------------------------------------------------------*/
61+
Name = name;
62+
63+
}
64+
65+
/*==========================================================================================================================
66+
| NAME
67+
\-------------------------------------------------------------------------------------------------------------------------*/
68+
/// <inheritdoc cref="MemberInfo.Name"/>
69+
internal string Name { get; }
70+
71+
/*==========================================================================================================================
72+
| TYPE
73+
\-------------------------------------------------------------------------------------------------------------------------*/
74+
/// <summary>
75+
/// Gets the <see cref="Type"/> associated with this member. For properties and get methods, this is the return type. For
76+
/// set methods, this is the type of the parameter.
77+
/// </summary>
78+
/// <remarks>
79+
/// Ideally, the <see cref="Type"/> would be provided as part of the <see cref="ItemMetadata"/> constructor.
80+
/// Unfortunately, however, the logic for setting this type varies based on whether it is a parameter, a methor, or a
81+
/// property. As such, it makes more sense for this logic to be implemented in derived classes. To facilitate this, the
82+
/// <see cref="Type"/> property is provided with an initter, which will automatically set <see cref="IsNullable"/>, <see
83+
/// cref="IsList"/>, and <see cref="IsConvertible"/> when it is set. If this is not done properly, dependency classes will
84+
/// not work properly, and will likely fail. Since there are only two expected derived classes—<see cref="MemberAccessor"
85+
/// /> and <see cref="ParameterMetadata"/>—this shouldn't be a problem. To help avoid this scenario, a <see cref="
86+
/// ArgumentNullException"/> is thrown with instructions in the unexpected case that <see cref="Type"/> is not set.
87+
/// </remarks>
88+
public Type Type {
89+
get {
90+
return _type?? throw new ArgumentNullException(
91+
nameof(Type),
92+
$"This {nameof(Type)} property must be initialized by classes derived by {nameof(ItemMetadata)}"
93+
);
94+
}
95+
init {
96+
_type = value;
97+
IsNullable = !Type.IsValueType || Nullable.GetUnderlyingType(Type) != null;
98+
IsList = isList();
99+
IsConvertible = AttributeValueConverter.IsConvertible(Type);
100+
bool isList()
101+
=> typeof(IList).IsAssignableFrom(Type) || Type.IsGenericType && _listTypes.Contains(Type.GetGenericTypeDefinition());
102+
}
103+
}
104+
105+
/*==========================================================================================================================
106+
| IS NULLABLE?
107+
\-------------------------------------------------------------------------------------------------------------------------*/
108+
/// <summary>
109+
/// Determine if the member accepts null values.
110+
/// </summary>
111+
/// <remarks>
112+
/// If the <see cref="Type"/> is a reference type, then it will always accept null values; this doesn't detect C# 9.0
113+
/// nullable reference types.
114+
/// </remarks>
115+
internal bool IsNullable { get; init; }
116+
117+
/*==========================================================================================================================
118+
| IS LIST?
119+
\-------------------------------------------------------------------------------------------------------------------------*/
120+
/// <summary>
121+
/// Determine if the member is a <see cref="IList"/>, <see cref="IList{T}"/>, <see cref="IEnumerable{T}"/>, or <see cref="
122+
/// ICollection{T}"/>.
123+
/// </summary>
124+
internal bool IsList { get; init; }
125+
126+
/*==========================================================================================================================
127+
| IS CONVERTIBLE?
128+
\-------------------------------------------------------------------------------------------------------------------------*/
129+
/// <summary>
130+
/// Determine if the member is of a type that can be converted using the <see cref="AttributeValueConverter"/> class.
131+
/// </summary>
132+
internal bool IsConvertible { get; init; }
133+
134+
/*==========================================================================================================================
135+
| CUSTOM ATTRIBUTES
136+
\-------------------------------------------------------------------------------------------------------------------------*/
137+
/// <summary>
138+
/// Provides a cached list of custom attributes associated with member.
139+
/// </summary>
140+
internal List<Attribute> CustomAttributes {
141+
get {
142+
_customAttributes ??= _attributeProvider.GetCustomAttributes(true).OfType<Attribute>().ToList();
143+
return _customAttributes;
144+
}
145+
}
146+
147+
} //Class
148+
} //Namespace

OnTopic/Internal/Reflection/MemberAccessor.cs

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,13 @@ namespace OnTopic.Internal.Reflection {
1919
/// compatible with the property or method parameter type being set, nor does it make any effort to provide explicit
2020
/// conversions. Those capabilities will be handled by higher-level libraries.
2121
/// </remarks>
22-
internal class MemberAccessor {
22+
internal class MemberAccessor: ItemMetadata {
2323

2424
/*==========================================================================================================================
2525
| PRIVATE VARIABLES
2626
\-------------------------------------------------------------------------------------------------------------------------*/
2727
private Action<object, object?>? _setter;
2828
private Func<object, object?>? _getter;
29-
private List<Attribute> _customAttributes = default!;
3029

3130
/*==========================================================================================================================
3231
| CONSTRUCTOR
@@ -36,7 +35,7 @@ internal class MemberAccessor {
3635
/// instance.
3736
/// </summary>
3837
/// <param name="memberInfo">The <see cref="MemberInfo"/> associated with the <see cref="MemberAccessor"/>.</param>
39-
internal MemberAccessor(MemberInfo memberInfo) {
38+
internal MemberAccessor(MemberInfo memberInfo): base(memberInfo.Name, memberInfo) {
4039

4140
/*------------------------------------------------------------------------------------------------------------------------
4241
| Validate parameters
@@ -50,10 +49,8 @@ internal MemberAccessor(MemberInfo memberInfo) {
5049
| Set Properties
5150
\-----------------------------------------------------------------------------------------------------------------------*/
5251
MemberInfo = memberInfo;
53-
Name = MemberInfo.Name;
5452
MemberType = MemberInfo.MemberType;
5553
Type = GetType(memberInfo);
56-
IsNullable = !Type.IsValueType || Nullable.GetUnderlyingType(Type) != null;
5754

5855
}
5956

@@ -65,39 +62,12 @@ internal MemberAccessor(MemberInfo memberInfo) {
6562
/// </summary>
6663
internal MemberInfo MemberInfo { get; }
6764

68-
/*==========================================================================================================================
69-
| NAME
70-
\-------------------------------------------------------------------------------------------------------------------------*/
71-
/// <inheritdoc cref="MemberInfo.Name"/>
72-
internal string Name { get; }
73-
7465
/*==========================================================================================================================
7566
| MEMBER TYPE
7667
\-------------------------------------------------------------------------------------------------------------------------*/
7768
/// <inheritdoc cref="MemberInfo.MemberType"/>
7869
internal MemberTypes MemberType { get; }
7970

80-
/*==========================================================================================================================
81-
| TYPE
82-
\-------------------------------------------------------------------------------------------------------------------------*/
83-
/// <summary>
84-
/// Gets the <see cref="Type"/> associated with this member. For properties and get methods, this is the return type. For
85-
/// set methods, this is the type of the parameter.
86-
/// </summary>
87-
internal Type Type { get; }
88-
89-
/*==========================================================================================================================
90-
| IS NULLABLE?
91-
\-------------------------------------------------------------------------------------------------------------------------*/
92-
/// <summary>
93-
/// Determine if the member accepts null values.
94-
/// </summary>
95-
/// <remarks>
96-
/// If the <see cref="Type"/> is a reference type, then it will always accept null values; this doesn't detect C# 9.0
97-
/// nullable reference types.
98-
/// </remarks>
99-
internal bool IsNullable { get; }
100-
10171
/*==========================================================================================================================
10272
| CAN READ?
10373
\-------------------------------------------------------------------------------------------------------------------------*/
@@ -123,19 +93,6 @@ internal MemberAccessor(MemberInfo memberInfo) {
12393
/// </remarks>
12494
internal bool CanWrite { get; private set; }
12595

126-
/*==========================================================================================================================
127-
| CUSTOM ATTRIBUTES
128-
\-------------------------------------------------------------------------------------------------------------------------*/
129-
/// <summary>
130-
/// Provides a cached list of custom attributes associated with member.
131-
/// </summary>
132-
internal List<Attribute> CustomAttributes {
133-
get {
134-
_customAttributes ??= MemberInfo.GetCustomAttributes(true).OfType<Attribute>().ToList();
135-
return _customAttributes;
136-
}
137-
}
138-
13996
/*==========================================================================================================================
14097
| METHOD: IS SETTABLE?
14198
\-------------------------------------------------------------------------------------------------------------------------*/
@@ -152,9 +109,8 @@ internal bool IsSettable(Type? sourceType = null, bool allowConversion = false)
152109
return true;
153110
}
154111
if (allowConversion) {
155-
var isTargetCompatible = AttributeValueConverter.IsConvertible(Type);
156-
var isSourceCompatible = sourceType is null || AttributeValueConverter.IsConvertible(sourceType);
157-
return isTargetCompatible && isSourceCompatible;
112+
var isSourceConvertible = sourceType is null || AttributeValueConverter.IsConvertible(sourceType);
113+
return IsConvertible && isSourceConvertible;
158114
}
159115
return false;
160116
}

0 commit comments

Comments
 (0)