Skip to content

Commit bec7123

Browse files
committed
Established new ItemMetadata abstract class
In preparation for introducing a new `ParameterMetadata` class, I've moved common properties from `MemberAccessor` to a new abstract class, `ItemMetadata`. This includes `Type`, `IsNullable`, and `CustomAttributes`. With this in place, we'll be able to base `ItemConfiguration` off of `ItemMetadata` instead of (just) an `IEnumerable<Attribute>`, thus providing access to more metadata. This will also allow us to cache common metadata for parameters on the `TypeAccessor`, just as we do for members. The `Type` treatment is a bit goofy because determining the type for property members requires some logic that's specific to whether it's a `MethodInfo` or a `PropertyInfo`. Since this makes most sense in `MemberAccessor`, the `Type` class is given an `init` method so that it can be set by derived classes. If this is not done, however, classes will not behave as expected. As an `abstract` class, this should be easy to ensure; if this were a `public` class, we'd take a different approach to avoid inadvertent errors. An `ArgumentNullException` will be thrown if this is not done. As part of this, updated XML Docs to maintain the references to these base properties.
1 parent 6b8ed99 commit bec7123

4 files changed

Lines changed: 136 additions & 61 deletions

File tree

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
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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+
27+
/*==========================================================================================================================
28+
| CONSTRUCTOR
29+
\-------------------------------------------------------------------------------------------------------------------------*/
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="ItemMetadata"/> class associated with a <see cref="MemberInfo"/> or <see
32+
/// cref="ParameterInfo"/> instance.
33+
/// </summary>
34+
/// <param name="name">The <see cref="Name"/> of the <see cref="MemberInfo"/> or <see cref="ParameterInfo"/>.</param>
35+
/// <param name="attributeProvider">
36+
/// The <see cref="MemberInfo"/> or <see cref="ParameterInfo"/> associated with the <see cref="ItemMetadata"/>.
37+
/// </param>
38+
internal ItemMetadata(string name, ICustomAttributeProvider attributeProvider) {
39+
40+
/*------------------------------------------------------------------------------------------------------------------------
41+
| Set Fields
42+
\-----------------------------------------------------------------------------------------------------------------------*/
43+
_attributeProvider = attributeProvider;
44+
45+
/*------------------------------------------------------------------------------------------------------------------------
46+
| Set Properties
47+
\-----------------------------------------------------------------------------------------------------------------------*/
48+
Name = name;
49+
50+
}
51+
52+
/*==========================================================================================================================
53+
| NAME
54+
\-------------------------------------------------------------------------------------------------------------------------*/
55+
/// <inheritdoc cref="MemberInfo.Name"/>
56+
internal string Name { get; }
57+
58+
/*==========================================================================================================================
59+
| TYPE
60+
\-------------------------------------------------------------------------------------------------------------------------*/
61+
/// <summary>
62+
/// Gets the <see cref="Type"/> associated with this member. For properties and get methods, this is the return type. For
63+
/// set methods, this is the type of the parameter.
64+
/// </summary>
65+
/// <remarks>
66+
/// Ideally, the <see cref="Type"/> would be provided as part of the <see cref="ItemMetadata"/> constructor.
67+
/// Unfortunately, however, the logic for setting this type varies based on whether it is a parameter, a methor, or a
68+
/// property. As such, it makes more sense for this logic to be implemented in derived classes. To facilitate this, the
69+
/// <see cref="Type"/> property is provided with an initter, which will automatically set <see cref="IsNullable"/>, <see
70+
/// cref="IsList"/>, and <see cref="IsConvertible"/> when it is set. If this is not done properly, dependency classes will
71+
/// not work properly, and will likely fail. Since there are only two expected derived classes—<see cref="MemberAccessor"
72+
/// /> and <see cref="ParameterMetadata"/>—this shouldn't be a problem. To help avoid this scenario, a <see cref="
73+
/// ArgumentNullException"/> is thrown with instructions in the unexpected case that <see cref="Type"/> is not set.
74+
/// </remarks>
75+
public Type Type {
76+
get {
77+
return _type?? throw new ArgumentNullException(
78+
nameof(Type),
79+
$"This {nameof(Type)} property must be initialized by classes derived by {nameof(ItemMetadata)}"
80+
);
81+
}
82+
init {
83+
_type = value;
84+
IsNullable = !Type.IsValueType || Nullable.GetUnderlyingType(Type) != null;
85+
IsList = isList();
86+
IsConvertible = AttributeValueConverter.IsConvertible(Type);
87+
bool isList()
88+
=> typeof(IList).IsAssignableFrom(Type) || Type.IsGenericType && _listTypes.Contains(Type.GetGenericTypeDefinition());
89+
}
90+
}
91+
92+
/*==========================================================================================================================
93+
| IS NULLABLE?
94+
\-------------------------------------------------------------------------------------------------------------------------*/
95+
/// <summary>
96+
/// Determine if the member accepts null values.
97+
/// </summary>
98+
/// <remarks>
99+
/// If the <see cref="Type"/> is a reference type, then it will always accept null values; this doesn't detect C# 9.0
100+
/// nullable reference types.
101+
/// </remarks>
102+
internal bool IsNullable { get; init; }
103+
104+
/*==========================================================================================================================
105+
| CUSTOM ATTRIBUTES
106+
\-------------------------------------------------------------------------------------------------------------------------*/
107+
/// <summary>
108+
/// Provides a cached list of custom attributes associated with member.
109+
/// </summary>
110+
internal List<Attribute> CustomAttributes {
111+
get {
112+
_customAttributes ??= _attributeProvider.GetCustomAttributes(true).OfType<Attribute>().ToList();
113+
return _customAttributes;
114+
}
115+
}
116+
117+
} //Class
118+
} //Namespace

OnTopic/Internal/Reflection/MemberAccessor.cs

Lines changed: 2 additions & 45 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
\-------------------------------------------------------------------------------------------------------------------------*/

0 commit comments

Comments
 (0)