Skip to content

Commit 20fafb8

Browse files
committed
include type hierarchy in type checking (Implements)
1 parent e563b4c commit 20fafb8

File tree

3 files changed

+63
-41
lines changed

3 files changed

+63
-41
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Changed
1515

1616
- Bump _System.Text.Json_ from 7.0.3 to 8.0.0 (concerns _Aqua.Text.Json_)
17+
- Include type hierarchy in `Implements` type checking
1718

1819
### Deprecated
1920

src/Aqua/TypeExtensions/TypeExtensions.cs

+39-41
Original file line numberDiff line numberDiff line change
@@ -156,65 +156,63 @@ private static bool Implements(this Type type, Type interfaceType, Type[][] type
156156
? IsAssignableToGenericType(interfaceType, typeArgs)
157157
: interfaceType.IsAssignableFrom;
158158

159-
return isAssignableFromSpecifiedInterface(type)
159+
return GetTypeHierarchy(type).Any(isAssignableFromSpecifiedInterface)
160160
|| type.GetInterfaces().Any(isAssignableFromSpecifiedInterface);
161-
}
162161

163-
private static Func<Type, bool> IsAssignableToGenericTypeDefinition(Type interfaceTypeInfo, Type[][] typeArgs)
164-
{
165-
var genericArgumentsCount = interfaceTypeInfo.GetGenericArguments().Length;
162+
static IEnumerable<Type> GetTypeHierarchy(Type? t)
163+
{
164+
while (t is not null)
165+
{
166+
yield return t;
167+
t = t.BaseType;
168+
}
169+
}
166170

167-
return i =>
171+
static Func<Type, bool> IsAssignableToGenericTypeDefinition(Type interfaceTypeInfo, Type[][] typeArgs)
168172
{
169-
var genericArguments = i.GenericTypeArguments;
170-
var isAssignable = i.IsGenericType && genericArguments.Length == genericArgumentsCount;
171-
if (isAssignable)
173+
return i =>
172174
{
173-
try
175+
var isAssignable = false;
176+
if (i.IsGenericType)
174177
{
175-
isAssignable = interfaceTypeInfo.MakeGenericType(genericArguments).IsAssignableFrom(i);
178+
var typeDef = i.IsGenericTypeDefinition ? i : i.GetGenericTypeDefinition();
179+
isAssignable = typeDef == interfaceTypeInfo;
176180
}
177-
catch (ArgumentException)
181+
182+
if (isAssignable)
178183
{
179-
// justification:
180-
// https://stackoverflow.com/questions/4864496/checking-if-an-object-meets-a-generic-parameter-constraint/4864565#4864565
181-
isAssignable = false;
184+
typeArgs[0] = i.GenericTypeArguments;
182185
}
183-
}
184186

185-
if (isAssignable)
186-
{
187-
typeArgs[0] = genericArguments;
188-
}
189-
190-
return isAssignable;
191-
};
192-
}
193-
194-
private static Func<Type, bool> IsAssignableToGenericType(Type interfaceTypeInfo, Type[][] typeArgs)
195-
{
196-
var interfaceTypeDefinition = interfaceTypeInfo.GetGenericTypeDefinition();
197-
var interfaceGenericArguments = interfaceTypeInfo.GetGenericArguments();
187+
return isAssignable;
188+
};
189+
}
198190

199-
return i =>
191+
static Func<Type, bool> IsAssignableToGenericType(Type interfaceTypeInfo, Type[][] typeArgs)
200192
{
201-
if (i.IsGenericType && !i.IsGenericTypeDefinition)
193+
var interfaceTypeDefinition = interfaceTypeInfo.GetGenericTypeDefinition();
194+
var interfaceGenericArguments = interfaceTypeInfo.GetGenericArguments();
195+
196+
return i =>
202197
{
203-
var typeDefinition = i.GetGenericTypeDefinition();
204-
if (typeDefinition == interfaceTypeDefinition)
198+
if (i.IsGenericType && !i.IsGenericTypeDefinition)
205199
{
206-
var genericArguments = i.GetGenericArguments();
207-
var allArgumentsAreAssignable = Enumerable.Range(0, genericArguments.Length - 1)
208-
.All(index => Implements(genericArguments[index], interfaceGenericArguments[index], typeArgs));
209-
if (allArgumentsAreAssignable)
200+
var typeDefinition = i.GetGenericTypeDefinition();
201+
if (typeDefinition == interfaceTypeDefinition)
210202
{
211-
return true;
203+
var genericArguments = i.GetGenericArguments();
204+
var allArgumentsAreAssignable = Enumerable.Range(0, genericArguments.Length - 1)
205+
.All(index => Implements(genericArguments[index], interfaceGenericArguments[index], typeArgs));
206+
if (allArgumentsAreAssignable)
207+
{
208+
return true;
209+
}
212210
}
213211
}
214-
}
215212

216-
return false;
217-
};
213+
return false;
214+
};
215+
}
218216
}
219217

220218
/// <summary>

test/Aqua.Tests/TypeExtensions/When_reflecting_implemented_types.cs

+23
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace Aqua.Tests.TypeExtensions;
99
using System.Collections.Generic;
1010
using System.Linq;
1111
using System.Linq.Expressions;
12+
using System.Runtime.InteropServices;
1213
using Xunit;
1314

1415
public class When_reflecting_implemented_types
@@ -38,6 +39,10 @@ public class TestQueryable<T> : IQueryable<T>
3839
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
3940
}
4041

42+
public class GenericSubClass<TNew, TBase> : TestQueryable<TBase>
43+
{
44+
}
45+
4146
[Fact]
4247
public void Should_reflect_base_interface()
4348
{
@@ -113,4 +118,22 @@ public void Should_reflect_base_class()
113118

114119
result.ShouldBeTrue();
115120
}
121+
122+
[Fact]
123+
public void Should_reflect_generic_base_class()
124+
{
125+
var result = typeof(GenericSubClass<int, long>).Implements(typeof(TestQueryable<>), out var generics);
126+
127+
result.ShouldBeTrue();
128+
generics.ShouldHaveSingleItem().ShouldBe(typeof(long));
129+
}
130+
131+
[Fact]
132+
public void Should_reflect_generic_base_class_interface()
133+
{
134+
var result = typeof(GenericSubClass<int, long>).Implements(typeof(IQueryable<>), out var generics);
135+
136+
result.ShouldBeTrue();
137+
generics.ShouldHaveSingleItem().ShouldBe(typeof(long));
138+
}
116139
}

0 commit comments

Comments
 (0)