Skip to content

Commit b55afea

Browse files
committed
Extracts the type reflection logic into a dedicated class
1 parent 1829408 commit b55afea

File tree

2 files changed

+176
-136
lines changed

2 files changed

+176
-136
lines changed

Src/FluentAssertions/Common/TypeExtensions.cs

Lines changed: 13 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@ internal static class TypeExtensions
2424
private static readonly ConcurrentDictionary<Type, bool> HasValueSemanticsCache = new();
2525
private static readonly ConcurrentDictionary<Type, bool> TypeIsRecordCache = new();
2626

27-
private static readonly ConcurrentDictionary<(Type Type, MemberVisibility Visibility), IEnumerable<PropertyInfo>>
28-
NonPrivatePropertiesCache = new();
29-
30-
private static readonly ConcurrentDictionary<(Type Type, MemberVisibility Visibility), IEnumerable<FieldInfo>>
31-
NonPrivateFieldsCache = new();
27+
private static readonly ConcurrentDictionary<(Type Type, MemberVisibility Visibility), TypeMemberReflector>
28+
TypeMemberReflectorsCache = new();
3229

3330
public static bool IsDecoratedWith<TAttribute>(this Type type)
3431
where TAttribute : Attribute
@@ -221,143 +218,23 @@ public static FieldInfo FindField(this Type type, string fieldName)
221218

222219
public static IEnumerable<MemberInfo> GetNonPrivateMembers(this Type typeToReflect, MemberVisibility visibility)
223220
{
224-
return
225-
GetNonPrivateProperties(typeToReflect, visibility)
226-
.Concat<MemberInfo>(GetNonPrivateFields(typeToReflect, visibility))
227-
.ToArray();
221+
return GetTypeReflectorFor(typeToReflect, visibility).NonPrivateMembers;
228222
}
229223

230224
public static IEnumerable<PropertyInfo> GetNonPrivateProperties(this Type typeToReflect, MemberVisibility visibility)
231225
{
232-
return NonPrivatePropertiesCache.GetOrAdd((typeToReflect, visibility), static key =>
233-
{
234-
IEnumerable<PropertyInfo> query =
235-
from propertyInfo in GetPropertiesFromHierarchy(key.Type, key.Visibility)
236-
where HasNonPrivateGetter(propertyInfo)
237-
where !propertyInfo.IsIndexer()
238-
select propertyInfo;
239-
240-
return query.ToArray();
241-
});
242-
}
243-
244-
private static IEnumerable<PropertyInfo> GetPropertiesFromHierarchy(Type typeToReflect, MemberVisibility memberVisibility)
245-
{
246-
bool includeInternals = memberVisibility.HasFlag(MemberVisibility.Internal);
247-
248-
return GetMembersFromHierarchy(typeToReflect, type =>
249-
{
250-
return type
251-
.GetProperties(AllInstanceMembersFlag | BindingFlags.DeclaredOnly)
252-
.Where(property => property.GetMethod?.IsPrivate == false)
253-
.Where(property => includeInternals || property.GetMethod is { IsAssembly: false, IsFamilyOrAssembly: false })
254-
.ToArray();
255-
});
226+
return GetTypeReflectorFor(typeToReflect, visibility).NonPrivateProperties;
256227
}
257228

258229
public static IEnumerable<FieldInfo> GetNonPrivateFields(this Type typeToReflect, MemberVisibility visibility)
259230
{
260-
return NonPrivateFieldsCache.GetOrAdd((typeToReflect, visibility), static key =>
261-
{
262-
IEnumerable<FieldInfo> query =
263-
from fieldInfo in GetFieldsFromHierarchy(key.Type, key.Visibility)
264-
where !fieldInfo.IsPrivate
265-
where !fieldInfo.IsFamily
266-
select fieldInfo;
267-
268-
return query.ToArray();
269-
});
270-
}
271-
272-
private static IEnumerable<FieldInfo> GetFieldsFromHierarchy(Type typeToReflect, MemberVisibility memberVisibility)
273-
{
274-
bool includeInternals = memberVisibility.HasFlag(MemberVisibility.Internal);
275-
276-
return GetMembersFromHierarchy(typeToReflect, type =>
277-
{
278-
return type
279-
.GetFields(AllInstanceMembersFlag)
280-
.Where(field => !field.IsPrivate)
281-
.Where(field => includeInternals || (!field.IsAssembly && !field.IsFamilyOrAssembly))
282-
.ToArray();
283-
});
284-
}
285-
286-
private static IEnumerable<TMemberInfo> GetMembersFromHierarchy<TMemberInfo>(
287-
Type typeToReflect,
288-
Func<Type, IEnumerable<TMemberInfo>> getMembers)
289-
where TMemberInfo : MemberInfo
290-
{
291-
if (typeToReflect.IsInterface)
292-
{
293-
return GetInterfaceMembers(typeToReflect, getMembers);
294-
}
295-
296-
return GetClassMembers(typeToReflect, getMembers);
297-
}
298-
299-
private static List<TMemberInfo> GetInterfaceMembers<TMemberInfo>(Type typeToReflect,
300-
Func<Type, IEnumerable<TMemberInfo>> getMembers)
301-
where TMemberInfo : MemberInfo
302-
{
303-
List<TMemberInfo> members = new();
304-
305-
var considered = new List<Type>();
306-
var queue = new Queue<Type>();
307-
considered.Add(typeToReflect);
308-
queue.Enqueue(typeToReflect);
309-
310-
while (queue.Count > 0)
311-
{
312-
Type subType = queue.Dequeue();
313-
314-
foreach (Type subInterface in subType.GetInterfaces())
315-
{
316-
if (considered.Contains(subInterface))
317-
{
318-
continue;
319-
}
320-
321-
considered.Add(subInterface);
322-
queue.Enqueue(subInterface);
323-
}
324-
325-
IEnumerable<TMemberInfo> typeMembers = getMembers(subType);
326-
327-
IEnumerable<TMemberInfo> newPropertyInfos = typeMembers.Where(x => !members.Contains(x));
328-
329-
members.InsertRange(0, newPropertyInfos);
330-
}
331-
332-
return members;
333-
}
334-
335-
private static List<TMemberInfo> GetClassMembers<TMemberInfo>(Type typeToReflect,
336-
Func<Type, IEnumerable<TMemberInfo>> getMembers)
337-
where TMemberInfo : MemberInfo
338-
{
339-
List<TMemberInfo> members = new();
340-
341-
while (typeToReflect != null)
342-
{
343-
foreach (var memberInfo in getMembers(typeToReflect))
344-
{
345-
if (members.All(mi => mi.Name != memberInfo.Name))
346-
{
347-
members.Add(memberInfo);
348-
}
349-
}
350-
351-
typeToReflect = typeToReflect.BaseType;
352-
}
353-
354-
return members;
231+
return GetTypeReflectorFor(typeToReflect, visibility).NonPrivateFields;
355232
}
356233

357-
private static bool HasNonPrivateGetter(PropertyInfo propertyInfo)
234+
private static TypeMemberReflector GetTypeReflectorFor(Type typeToReflect, MemberVisibility visibility)
358235
{
359-
MethodInfo getMethod = propertyInfo.GetGetMethod(nonPublic: true);
360-
return getMethod is { IsPrivate: false, IsFamily: false };
236+
return TypeMemberReflectorsCache.GetOrAdd((typeToReflect, visibility),
237+
static key => new TypeMemberReflector(key.Type, key.Visibility));
361238
}
362239

363240
/// <summary>
@@ -404,11 +281,6 @@ public static MethodInfo GetParameterlessMethod(this Type type, string methodNam
404281
return type.GetMethod(methodName, Enumerable.Empty<Type>());
405282
}
406283

407-
public static bool HasParameterlessMethod(this Type type, string methodName)
408-
{
409-
return type.GetParameterlessMethod(methodName) is not null;
410-
}
411-
412284
public static PropertyInfo FindPropertyByName(this Type type, string propertyName)
413285
{
414286
return type.GetProperty(propertyName, AllStaticAndInstanceMembersFlag);
@@ -426,6 +298,11 @@ public static bool HasExplicitlyImplementedProperty(this Type type, Type interfa
426298
return hasGetter || hasSetter;
427299
}
428300

301+
private static bool HasParameterlessMethod(this Type type, string methodName)
302+
{
303+
return type.GetParameterlessMethod(methodName) is not null;
304+
}
305+
429306
public static PropertyInfo GetIndexerByParameterTypes(this Type type, IEnumerable<Type> parameterTypes)
430307
{
431308
return type.GetProperties(AllStaticAndInstanceMembersFlag)
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using FluentAssertions.Equivalency;
6+
7+
namespace FluentAssertions.Common;
8+
9+
/// <summary>
10+
/// Helper class to get all the public and internal fields and properties from a type.
11+
/// </summary>
12+
internal sealed class TypeMemberReflector
13+
{
14+
private const BindingFlags AllInstanceMembersFlag =
15+
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
16+
17+
private readonly Type typeToReflect;
18+
private readonly MemberVisibility visibility;
19+
20+
public TypeMemberReflector(Type typeToReflect, MemberVisibility visibility)
21+
{
22+
this.typeToReflect = typeToReflect;
23+
this.visibility = visibility;
24+
25+
LoadNonPrivateProperties();
26+
LoadNonPrivateFields();
27+
28+
NonPrivateMembers = NonPrivateProperties.Concat<MemberInfo>(NonPrivateFields).ToArray();
29+
}
30+
31+
public MemberInfo[] NonPrivateMembers { get; }
32+
33+
public PropertyInfo[] NonPrivateProperties { get; private set; }
34+
35+
public FieldInfo[] NonPrivateFields { get; private set; }
36+
37+
private void LoadNonPrivateProperties()
38+
{
39+
IEnumerable<PropertyInfo> query =
40+
from propertyInfo in GetPropertiesFromHierarchy(typeToReflect, visibility)
41+
where HasNonPrivateGetter(propertyInfo)
42+
where !propertyInfo.IsIndexer()
43+
select propertyInfo;
44+
45+
NonPrivateProperties = query.ToArray();
46+
}
47+
48+
private static IEnumerable<PropertyInfo> GetPropertiesFromHierarchy(Type typeToReflect, MemberVisibility memberVisibility)
49+
{
50+
bool includeInternals = memberVisibility.HasFlag(MemberVisibility.Internal);
51+
52+
return GetMembersFromHierarchy(typeToReflect, type =>
53+
{
54+
return type
55+
.GetProperties(AllInstanceMembersFlag | BindingFlags.DeclaredOnly)
56+
.Where(property => property.GetMethod?.IsPrivate == false)
57+
.Where(property => includeInternals || property.GetMethod is { IsAssembly: false, IsFamilyOrAssembly: false })
58+
.ToArray();
59+
});
60+
}
61+
62+
private void LoadNonPrivateFields()
63+
{
64+
IEnumerable<FieldInfo> query =
65+
from fieldInfo in GetFieldsFromHierarchy(typeToReflect, visibility)
66+
where !fieldInfo.IsPrivate
67+
where !fieldInfo.IsFamily
68+
select fieldInfo;
69+
70+
NonPrivateFields = query.ToArray();
71+
}
72+
73+
private static IEnumerable<FieldInfo> GetFieldsFromHierarchy(Type typeToReflect, MemberVisibility memberVisibility)
74+
{
75+
bool includeInternals = memberVisibility.HasFlag(MemberVisibility.Internal);
76+
77+
return GetMembersFromHierarchy(typeToReflect, type =>
78+
{
79+
return type
80+
.GetFields(AllInstanceMembersFlag)
81+
.Where(field => !field.IsPrivate)
82+
.Where(field => includeInternals || (!field.IsAssembly && !field.IsFamilyOrAssembly))
83+
.ToArray();
84+
});
85+
}
86+
87+
private static IEnumerable<TMemberInfo> GetMembersFromHierarchy<TMemberInfo>(
88+
Type typeToReflect,
89+
Func<Type, IEnumerable<TMemberInfo>> getMembers)
90+
where TMemberInfo : MemberInfo
91+
{
92+
if (typeToReflect.IsInterface)
93+
{
94+
return GetInterfaceMembers(typeToReflect, getMembers);
95+
}
96+
97+
return GetClassMembers(typeToReflect, getMembers);
98+
}
99+
100+
private static List<TMemberInfo> GetInterfaceMembers<TMemberInfo>(Type typeToReflect,
101+
Func<Type, IEnumerable<TMemberInfo>> getMembers)
102+
where TMemberInfo : MemberInfo
103+
{
104+
List<TMemberInfo> members = new();
105+
106+
var considered = new List<Type>();
107+
var queue = new Queue<Type>();
108+
considered.Add(typeToReflect);
109+
queue.Enqueue(typeToReflect);
110+
111+
while (queue.Count > 0)
112+
{
113+
Type subType = queue.Dequeue();
114+
115+
foreach (Type subInterface in subType.GetInterfaces())
116+
{
117+
if (considered.Contains(subInterface))
118+
{
119+
continue;
120+
}
121+
122+
considered.Add(subInterface);
123+
queue.Enqueue(subInterface);
124+
}
125+
126+
IEnumerable<TMemberInfo> typeMembers = getMembers(subType);
127+
128+
IEnumerable<TMemberInfo> newPropertyInfos = typeMembers.Where(x => !members.Contains(x));
129+
130+
members.InsertRange(0, newPropertyInfos);
131+
}
132+
133+
return members;
134+
}
135+
136+
private static List<TMemberInfo> GetClassMembers<TMemberInfo>(Type typeToReflect,
137+
Func<Type, IEnumerable<TMemberInfo>> getMembers)
138+
where TMemberInfo : MemberInfo
139+
{
140+
List<TMemberInfo> members = new();
141+
142+
while (typeToReflect != null)
143+
{
144+
foreach (var memberInfo in getMembers(typeToReflect))
145+
{
146+
if (members.All(mi => mi.Name != memberInfo.Name))
147+
{
148+
members.Add(memberInfo);
149+
}
150+
}
151+
152+
typeToReflect = typeToReflect.BaseType;
153+
}
154+
155+
return members;
156+
}
157+
158+
private static bool HasNonPrivateGetter(PropertyInfo propertyInfo)
159+
{
160+
MethodInfo getMethod = propertyInfo.GetGetMethod(nonPublic: true);
161+
return getMethod is { IsPrivate: false, IsFamily: false };
162+
}
163+
}

0 commit comments

Comments
 (0)