Skip to content

Commit f397b56

Browse files
committed
Including inherited members in all member retrieval / Simplifying MemberInfo methods
1 parent c17117d commit f397b56

File tree

9 files changed

+265
-126
lines changed

9 files changed

+265
-126
lines changed

NetStandardPolyfills.UnitTests.Net40/WhenRetrievingTypeProperties.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ public override void ShouldRetrieveANonPublicStaticPropertyByName() =>
2727
[Test]
2828
public override void ShouldRetrievePublicInstanceProperties() => DoShouldRetrievePublicInstanceProperties();
2929

30+
[Test]
31+
public override void ShouldRetrieveInheritedPublicInstanceProperties() =>
32+
DoShouldRetrieveInheritedPublicInstanceProperties();
33+
34+
[Test]
35+
public override void ShouldRetrieveOverriddenPublicInstanceProperties() =>
36+
DoShouldRetrieveOverriddenPublicInstanceProperties();
37+
3038
[Test]
3139
public override void ShouldRetrieveAPublicInstancePropertyByName() => DoShouldRetrieveAPublicInstancePropertyByName();
3240

NetStandardPolyfills.UnitTests.NetCore/WhenRetrievingTypeProperties.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ public override void ShouldRetrieveANonPublicStaticPropertyByName() =>
2626
[Fact]
2727
public override void ShouldRetrievePublicInstanceProperties() => DoShouldRetrievePublicInstanceProperties();
2828

29+
[Fact]
30+
public override void ShouldRetrieveInheritedPublicInstanceProperties() =>
31+
DoShouldRetrieveInheritedPublicInstanceProperties();
32+
33+
[Fact]
34+
public override void ShouldRetrieveOverriddenPublicInstanceProperties() =>
35+
DoShouldRetrieveOverriddenPublicInstanceProperties();
36+
2937
[Fact]
3038
public override void ShouldRetrieveAPublicInstancePropertyByName() => DoShouldRetrieveAPublicInstancePropertyByName();
3139

NetStandardPolyfills.UnitTests/PropertyTestsBase.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ public abstract class PropertyTestsBase
1919

2020
public abstract void ShouldRetrievePublicInstanceProperties();
2121

22+
public abstract void ShouldRetrieveInheritedPublicInstanceProperties();
23+
24+
public abstract void ShouldRetrieveOverriddenPublicInstanceProperties();
25+
2226
public abstract void ShouldRetrieveAPublicInstancePropertyByName();
2327

2428
public abstract void ShouldExcludeAPublicInstancePropertyByName();
@@ -97,6 +101,22 @@ protected void DoShouldRetrievePublicInstanceProperties()
97101
.ShouldNotBeNull();
98102
}
99103

104+
protected void DoShouldRetrieveInheritedPublicInstanceProperties()
105+
{
106+
typeof(Derived)
107+
.GetPublicInstanceProperties()
108+
.Where(p => p.Name == "Id")
109+
.ShouldHaveSingleItem();
110+
}
111+
112+
protected void DoShouldRetrieveOverriddenPublicInstanceProperties()
113+
{
114+
typeof(DerivedOverridden)
115+
.GetPublicInstanceProperties()
116+
.Where(p => p.Name == "Id")
117+
.ShouldHaveSingleItem();
118+
}
119+
100120
protected void DoShouldRetrieveAPublicInstancePropertyByName()
101121
{
102122
typeof(TestHelper)
@@ -220,5 +240,29 @@ protected void DoShouldRetrieveANonPublicSetAccessor()
220240
}
221241

222242
#endregion
243+
244+
#region Helper Classes
245+
246+
// ReSharper disable UnusedMember.Local
247+
private abstract class EntityBase
248+
{
249+
public virtual int Id { get; set; }
250+
}
251+
252+
private class Derived : EntityBase
253+
{
254+
public string Name { get; set; }
255+
}
256+
257+
private class DerivedOverridden : EntityBase
258+
{
259+
public override int Id { get; set; }
260+
261+
public string Name { get; set; }
262+
}
263+
264+
// ReSharper restore UnusedMember.Local
265+
266+
#endregion
223267
}
224268
}

NetStandardPolyfills/ConstructorExtensionsPolyfill.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static class ConstructorExtensionsPolyfill
2020
public static IEnumerable<ConstructorInfo> GetPublicInstanceConstructors(this Type type)
2121
{
2222
#if NET_STANDARD
23-
return type.GetTypeInfo().DeclaredConstructors.Where(c => c.IsPublic && !c.IsStatic);
23+
return GetConstructors(type, isPublic: true, isStatic: false);
2424
#else
2525
return type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
2626
#endif
@@ -57,7 +57,7 @@ public static ConstructorInfo GetPublicInstanceConstructor(this Type type, param
5757
public static IEnumerable<ConstructorInfo> GetNonPublicInstanceConstructors(this Type type)
5858
{
5959
#if NET_STANDARD
60-
return type.GetTypeInfo().DeclaredConstructors.Where(c => !c.IsPublic && !c.IsStatic);
60+
return GetConstructors(type, isPublic: false, isStatic: false);
6161
#else
6262
return type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
6363
#endif
@@ -91,6 +91,9 @@ public static ConstructorInfo GetNonPublicInstanceConstructor(this Type type, pa
9191
}
9292

9393
#if NET_STANDARD
94+
internal static IEnumerable<ConstructorInfo> GetConstructors(this Type type, bool isPublic, bool isStatic)
95+
=> type.GetTypeInfo().DeclaredConstructors.Where(c => (c.IsPublic == isPublic) && (c.IsStatic == isStatic));
96+
9497
private static ConstructorInfo GetConstructorWithTypes(this IEnumerable<ConstructorInfo> constructors, Type[] parameterTypes)
9598
{
9699
return constructors

NetStandardPolyfills/FieldExtensionsPolyfill.cs

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Reflection;
7+
#if !NET_STANDARD
8+
using static System.Reflection.BindingFlags;
9+
#endif
710

811
/// <summary>
912
/// Provides a set of static methods for obtaining field information in .NET Standard 1.0 and .NET 4.0.
@@ -18,9 +21,9 @@ public static class FieldExtensionsPolyfill
1821
public static IEnumerable<FieldInfo> GetPublicStaticFields(this Type type)
1922
{
2023
#if NET_STANDARD
21-
return type.GetTypeInfo().DeclaredFields.Where(f => f.IsPublic && f.IsStatic);
24+
return GetFields(type, isPublic: true, isStatic: true);
2225
#else
23-
return type.GetFields(BindingFlags.Public | BindingFlags.Static);
26+
return GetFields(type, Public | Static);
2427
#endif
2528
}
2629

@@ -36,9 +39,9 @@ public static IEnumerable<FieldInfo> GetPublicStaticFields(this Type type)
3639
public static FieldInfo GetPublicStaticField(this Type type, string name)
3740
{
3841
#if NET_STANDARD
39-
return type.GetPublicStaticFields().FirstOrDefault(f => f.Name == name);
42+
return GetFields(type, name, isPublic: true, isStatic: true).FirstOrDefault();
4043
#else
41-
return type.GetField(name, BindingFlags.Public | BindingFlags.Static);
44+
return GetFields(type, name, Public | Static).FirstOrDefault();
4245
#endif
4346
}
4447

@@ -50,9 +53,9 @@ public static FieldInfo GetPublicStaticField(this Type type, string name)
5053
public static IEnumerable<FieldInfo> GetPublicInstanceFields(this Type type)
5154
{
5255
#if NET_STANDARD
53-
return type.GetTypeInfo().DeclaredFields.Where(f => f.IsPublic && !f.IsStatic);
56+
return GetFields(type, isPublic: true, isStatic: false);
5457
#else
55-
return type.GetFields(BindingFlags.Public | BindingFlags.Instance);
58+
return GetFields(type, Public | Instance);
5659
#endif
5760
}
5861

@@ -68,9 +71,9 @@ public static IEnumerable<FieldInfo> GetPublicInstanceFields(this Type type)
6871
public static FieldInfo GetPublicInstanceField(this Type type, string name)
6972
{
7073
#if NET_STANDARD
71-
return type.GetPublicInstanceFields().FirstOrDefault(f => f.Name == name);
74+
return GetFields(type, name, isPublic: true, isStatic: false).FirstOrDefault();
7275
#else
73-
return type.GetField(name, BindingFlags.Public | BindingFlags.Instance);
76+
return GetFields(type, name, Public | Instance).FirstOrDefault();
7477
#endif
7578
}
7679

@@ -82,15 +85,9 @@ public static FieldInfo GetPublicInstanceField(this Type type, string name)
8285
public static IEnumerable<FieldInfo> GetNonPublicStaticFields(this Type type)
8386
{
8487
#if NET_STANDARD
85-
return type
86-
.GetTypeInfo()
87-
.DeclaredFields
88-
.Where(f => !f.IsPublic && f.IsStatic)
89-
.WithoutAutoGeneratedBackingFields();
88+
return GetFields(type, isPublic: false, isStatic: true);
9089
#else
91-
return type
92-
.GetFields(BindingFlags.NonPublic | BindingFlags.Static)
93-
.WithoutAutoGeneratedBackingFields();
90+
return GetFields(type, NonPublic | Static);
9491
#endif
9592
}
9693

@@ -106,9 +103,9 @@ public static IEnumerable<FieldInfo> GetNonPublicStaticFields(this Type type)
106103
public static FieldInfo GetNonPublicStaticField(this Type type, string name)
107104
{
108105
#if NET_STANDARD
109-
return type.GetNonPublicStaticFields().FirstOrDefault(f => f.Name == name);
106+
return GetFields(type, name, isPublic: false, isStatic: true).FirstOrDefault();
110107
#else
111-
return type.GetField(name, BindingFlags.NonPublic | BindingFlags.Static);
108+
return GetFields(type, name, NonPublic | Static).FirstOrDefault();
112109
#endif
113110
}
114111

@@ -120,15 +117,9 @@ public static FieldInfo GetNonPublicStaticField(this Type type, string name)
120117
public static IEnumerable<FieldInfo> GetNonPublicInstanceFields(this Type type)
121118
{
122119
#if NET_STANDARD
123-
return type
124-
.GetTypeInfo()
125-
.DeclaredFields
126-
.Where(f => !f.IsPublic && !f.IsStatic)
127-
.WithoutAutoGeneratedBackingFields();
120+
return GetFields(type, isPublic: false, isStatic: false);
128121
#else
129-
return type
130-
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
131-
.WithoutAutoGeneratedBackingFields();
122+
return GetFields(type, NonPublic | Instance);
132123
#endif
133124
}
134125

@@ -144,15 +135,57 @@ public static IEnumerable<FieldInfo> GetNonPublicInstanceFields(this Type type)
144135
public static FieldInfo GetNonPublicInstanceField(this Type type, string name)
145136
{
146137
#if NET_STANDARD
147-
return type.GetNonPublicInstanceFields().FirstOrDefault(f => f.Name == name);
138+
return GetFields(type, name, isPublic: false, isStatic: false).FirstOrDefault();
148139
#else
149-
return type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
140+
return GetFields(type, name, NonPublic | Instance).FirstOrDefault();
150141
#endif
151142
}
152143

153-
private static IEnumerable<FieldInfo> WithoutAutoGeneratedBackingFields(this IEnumerable<FieldInfo> fields)
144+
#region Helper Methods
145+
146+
#if NET_STANDARD
147+
internal static IEnumerable<FieldInfo> GetFields(this Type type, bool isPublic, bool isStatic)
148+
=> GetFields(type, name: null, isPublic: isPublic, isStatic: isStatic);
149+
150+
private static IEnumerable<FieldInfo> GetFields(Type type, string name, bool isPublic, bool isStatic)
154151
{
155-
return fields.Where(f => f.Name[0] != '<');
152+
return GetFields(
153+
type,
154+
t => t.GetTypeInfo().DeclaredFields,
155+
name,
156+
f => (f.IsPublic == isPublic) && (f.IsStatic == isStatic));
156157
}
158+
#else
159+
private static IEnumerable<FieldInfo> GetFields(Type type, BindingFlags bindingFlags)
160+
=> GetFields(type, name: null, bindingFlags: bindingFlags);
161+
162+
private static IEnumerable<FieldInfo> GetFields(Type type, string name, BindingFlags bindingFlags)
163+
=> GetFields(type, t => t.GetFields(bindingFlags), name);
164+
#endif
165+
private static IEnumerable<FieldInfo> GetFields(
166+
Type type,
167+
Func<Type, IEnumerable<FieldInfo>> fieldsFactory,
168+
string name,
169+
Func<FieldInfo, bool> fieldFilter = null)
170+
{
171+
if (fieldFilter == null)
172+
{
173+
fieldFilter = f => true;
174+
}
175+
176+
return MemberFinder<FieldInfo>.EnumerateMembers(
177+
type,
178+
fieldsFactory,
179+
name,
180+
f => IsNotAutoGeneratedBackingField(f) && fieldFilter.Invoke(f),
181+
FieldNotAlreadyIncluded);
182+
}
183+
184+
private static bool IsNotAutoGeneratedBackingField(this MemberInfo field) => field.Name[0] != '<';
185+
186+
private static bool FieldNotAlreadyIncluded(FieldInfo field, ICollection<FieldInfo> fieldsSoFar)
187+
=> (fieldsSoFar.Count == 0) || fieldsSoFar.All(f => f.Name != field.Name);
188+
189+
#endregion
157190
}
158191
}

NetStandardPolyfills/MemberExtensionsPolyfill.cs

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -214,44 +214,15 @@ public static MemberInfo GetNonPublicInstanceMember(this Type type, string name)
214214
#if NET_STANDARD
215215
private static IEnumerable<MemberInfo> GetMembers(this Type type, bool isPublic, bool isStatic)
216216
{
217-
return type
218-
.GetTypeInfo()
219-
.DeclaredMembers
220-
.Select(m => new
221-
{
222-
Member = m,
223-
AccessibilityAndScope = GetAccessibilityAndScopeTuple(m)
224-
})
225-
.Where(d => (d.AccessibilityAndScope.IsPublic() == isPublic) &&
226-
(d.AccessibilityAndScope.IsStatic() == isStatic))
227-
.Select(d => d.Member);
228-
}
229-
230-
private static Tuple<bool, bool> GetAccessibilityAndScopeTuple(MemberInfo member)
231-
{
232-
switch (member)
233-
{
234-
case PropertyInfo property:
235-
return Tuple.Create(property.IsPublic(), property.IsStatic());
236-
237-
case FieldInfo field:
238-
return Tuple.Create(field.IsPublic, field.IsStatic);
217+
var members = new List<MemberInfo>();
239218

240-
case MethodInfo method:
241-
return Tuple.Create(method.IsPublic, method.IsStatic);
219+
members.AddRange(type.GetConstructors(isPublic, isStatic));
220+
members.AddRange(type.GetFields(isPublic, isStatic));
221+
members.AddRange(type.GetProperties(isPublic, isStatic));
222+
members.AddRange(type.GetMethods(isPublic, isStatic));
242223

243-
case ConstructorInfo constructor:
244-
return Tuple.Create(constructor.IsPublic, constructor.IsStatic);
245-
}
246-
247-
return Tuple.Create(false, false);
224+
return members;
248225
}
249-
250-
private static bool IsPublic(this Tuple<bool, bool> accessibilityAndScopeTuple) =>
251-
accessibilityAndScopeTuple.Item1;
252-
253-
private static bool IsStatic(this Tuple<bool, bool> accessibilityAndScopeTuple) =>
254-
accessibilityAndScopeTuple.Item2;
255226
#endif
256227
private static MemberInfo GetSingleMember(this IEnumerable<MemberInfo> members, string name)
257228
{
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
namespace AgileObjects.NetStandardPolyfills
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
8+
internal static class MemberFinder<TMember>
9+
where TMember : MemberInfo
10+
{
11+
public static IEnumerable<TMember> EnumerateMembers(
12+
Type type,
13+
Func<Type, IEnumerable<TMember>> membersFactory,
14+
string name,
15+
Func<TMember, bool> memberFilter,
16+
Func<TMember, ICollection<TMember>, bool> uniqueMemberFilter)
17+
{
18+
if (memberFilter == null)
19+
{
20+
memberFilter = m => true;
21+
}
22+
23+
var membersSoFar = new List<TMember>();
24+
25+
while (type != null)
26+
{
27+
var members = membersFactory
28+
.Invoke(type)
29+
.Where(memberFilter.Invoke)
30+
.Where(m => uniqueMemberFilter.Invoke(m, membersSoFar));
31+
32+
if (name != null)
33+
{
34+
members = members.Where(m => m.Name == name);
35+
}
36+
37+
var matchingMembers = members.ToArray();
38+
39+
membersSoFar.AddRange(matchingMembers);
40+
41+
foreach (var member in matchingMembers)
42+
{
43+
yield return member;
44+
}
45+
46+
type = type.GetBaseType();
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)