Skip to content

Commit 971370b

Browse files
committed
Improve performance of IsNullable/IsOfGenericType/IsOfGenericInterface etc methods in TypeHelper class
# Conflicts: # Orm/Xtensive.Orm/Orm/Linq/QueryHelper.cs
1 parent e5af27a commit 971370b

4 files changed

Lines changed: 198 additions & 31 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using NUnit.Framework;
4+
using Xtensive.Reflection;
5+
6+
namespace Xtensive.Orm.Tests.Core.Reflection
7+
{
8+
[TestFixture]
9+
public class TypeHelperGetGenericInterfaceTests
10+
{
11+
private class ListInt : List<int>
12+
{ }
13+
14+
private class ListIntLvl1 : ListInt
15+
{ }
16+
17+
private class GenericList<T> : List<T>
18+
{ }
19+
20+
private class GenericListLvl1<T> : GenericList<T>
21+
{ }
22+
23+
private class GenericListLvl1Int : GenericListLvl1<int>
24+
{ }
25+
26+
private class GenericListInt : GenericList<int>
27+
{ }
28+
29+
private class GenericListIntLvl1 : GenericListInt
30+
{ }
31+
32+
[Test]
33+
public void InterfaceIsDiscoverable_OnOpenGenericType() =>
34+
Assert.IsNotNull(typeof(List<>).GetGenericInterface(typeof(IList<>)));
35+
36+
[Test]
37+
public void InterfaceIsDiscoverable_OnOpenGenericInterface() =>
38+
Assert.IsNotNull(typeof(IList<>).GetGenericInterface(typeof(ICollection<>)));
39+
40+
[Test]
41+
public void InterfaceIsDiscoverable_OnClosedGenericType() =>
42+
Assert.AreSame(typeof(List<int>).GetGenericInterface(typeof(IList<>)), typeof(IList<int>));
43+
44+
[Test]
45+
public void InterfaceIsDiscoverable_OnClosedGenericInterface() =>
46+
Assert.AreSame(typeof(IList<int>).GetGenericInterface(typeof(ICollection<>)), typeof(ICollection<int>));
47+
48+
[Test]
49+
public void InterfaceIsDiscoverable_OnItself() =>
50+
Assert.AreSame(typeof(IList<int>).GetGenericInterface(typeof(IList<>)), typeof(IList<int>));
51+
52+
[TestCase(typeof(ListInt))]
53+
[TestCase(typeof(ListIntLvl1))]
54+
[TestCase(typeof(GenericList<int>))]
55+
[TestCase(typeof(GenericListLvl1<int>))]
56+
[TestCase(typeof(GenericListLvl1Int))]
57+
[TestCase(typeof(GenericListInt))]
58+
[TestCase(typeof(GenericListIntLvl1))]
59+
public void InterfaceIsDiscoverable_OnAnyAncestorOfAnImplementor(Type type) =>
60+
Assert.AreSame(type.GetGenericInterface(typeof(IList<>)), typeof(IList<int>));
61+
62+
[Test]
63+
public void NullIsReturnedIfNoMatchFound() =>
64+
Assert.IsNull(typeof(ICollection<int>).GetGenericInterface(typeof(IList<>)));
65+
66+
[Test]
67+
public void NullIsAcceptedAsFirstParameter() =>
68+
Assert.IsNull(TypeHelper.GetGenericInterface(null, typeof(List<>)));
69+
}
70+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using NUnit.Framework;
4+
using Xtensive.Reflection;
5+
6+
namespace Xtensive.Orm.Tests.Core.Reflection
7+
{
8+
[TestFixture]
9+
public class TypeHelperGetGenericTypeTests
10+
{
11+
private class ListInt : List<int>
12+
{ }
13+
14+
private class ListIntLvl1 : ListInt
15+
{ }
16+
17+
private class GenericList<T> : List<T>
18+
{ }
19+
20+
private class GenericListLvl1<T> : GenericList<T>
21+
{ }
22+
23+
private class GenericListLvl1Int : GenericListLvl1<int>
24+
{ }
25+
26+
private class GenericListInt : GenericList<int>
27+
{ }
28+
29+
private class GenericListIntLvl1 : GenericListInt
30+
{ }
31+
32+
[Test]
33+
public void ParameterizedGenericTypeIsDiscoverable_ByItself() =>
34+
Assert.AreSame(typeof(List<int>).GetGenericType(typeof(List<>)), typeof(List<int>));
35+
36+
[Test]
37+
public void ParameterizedGenericTypeIsDiscoverable_ByItsDirectNonGenericAncestor() =>
38+
Assert.AreSame(typeof(ListInt).GetGenericType(typeof(List<>)), typeof(List<int>));
39+
40+
[Test]
41+
public void ParameterizedGenericTypeIsDiscoverable_ByItsIndirectNonGenericAncestor() =>
42+
Assert.AreSame(typeof(ListIntLvl1).GetGenericType(typeof(List<>)), typeof(List<int>));
43+
44+
[Test]
45+
public void ParameterizedGenericTypeIsDiscoverable_ByItsDirectGenericAncestor() =>
46+
Assert.AreSame(typeof(GenericList<int>).GetGenericType(typeof(List<>)), typeof(List<int>));
47+
48+
[Test]
49+
public void ParameterizedGenericTypeIsDiscoverable_ByItsIndirectGenericAncestor() =>
50+
Assert.AreSame(typeof(GenericListLvl1<int>).GetGenericType(typeof(List<>)), typeof(List<int>));
51+
52+
[Test]
53+
public void ParameterizedGenericTypeIsDiscoverable_ByAnAncestorOfItsDirectGenericAncestor() =>
54+
Assert.AreSame(typeof(GenericListIntLvl1).GetGenericType(typeof(List<>)), typeof(List<int>));
55+
56+
[Test]
57+
public void ParameterizedGenericTypeIsDiscoverable_ByAnAncestorOfItsIndirectGenericAncestor() =>
58+
Assert.AreSame(typeof(GenericListLvl1Int).GetGenericType(typeof(List<>)), typeof(List<int>));
59+
60+
[Test]
61+
public void ParameterizedGenericInterfaceIsDiscoverable_ByItself() =>
62+
Assert.AreSame(typeof(IList<int>).GetGenericType(typeof(IList<>)), typeof(IList<int>));
63+
64+
[Test]
65+
public void ParameterizedGenericInterfaceIsNotDiscoverable_ByItsImplementation() =>
66+
Assert.IsNull(typeof(List<int>).GetGenericType(typeof(IList<>)));
67+
68+
[Test]
69+
public void NullIsReturnedIfNoMatchFound() =>
70+
Assert.IsNull(typeof(Stack<int>).GetGenericType(typeof(List<>)));
71+
72+
[Test]
73+
public void NullIsAcceptedAsFirstParameter() =>
74+
Assert.IsNull(TypeHelper.GetGenericType(null, typeof(List<>)));
75+
}
76+
}

Orm/Xtensive.Orm/Orm/Linq/QueryHelper.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,7 @@ public static void TryAddConvarianceCast(ref Expression source, Type baseType)
184184

185185
public static Type GetSequenceElementType(Type type)
186186
{
187-
var sequenceType = type.GetGenericType(typeof (IEnumerable<>))
188-
?? type.GetInterfaces().Select(i => i.GetGenericType(typeof (IEnumerable<>))).FirstOrDefault(i => i!=null);
187+
var sequenceType = type.GetGenericInterface(typeof (IEnumerable<>));
189188
return sequenceType!=null ? sequenceType.GetGenericArguments()[0] : null;
190189
}
191190

Orm/Xtensive.Orm/Reflection/TypeHelper.cs

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public static class TypeHelper
3535
private static readonly Type ArrayType = typeof(Array);
3636
private static readonly Type EnumType = typeof(Enum);
3737
private static readonly Type NullableType = typeof(Nullable<>);
38+
private static readonly int NullableTypeMetadataToken = NullableType.MetadataToken;
39+
private static readonly Module NullableTypeModule = NullableType.Module;
3840
private static readonly Type CompilerGeneratedAttributeType = typeof(CompilerGeneratedAttribute);
3941
private static readonly string TypeHelperNamespace = typeof(TypeHelper).Namespace;
4042

@@ -855,7 +857,7 @@ private static string GetShortNameBase(this Type type)
855857
/// <returns><see langword="True"/> if type is nullable type;
856858
/// otherwise, <see langword="false"/>.</returns>
857859
public static bool IsNullable(this Type type) =>
858-
type.IsGenericType && ReferenceEquals(type.GetGenericTypeDefinition(), NullableType);
860+
(type.MetadataToken ^ NullableTypeMetadataToken) == 0 && ReferenceEquals(type.Module, NullableTypeModule);
859861

860862
/// <summary>
861863
/// Indicates whether <typeparamref name="T"/> type is a <see cref="Nullable{T}"/> type.
@@ -890,34 +892,36 @@ public static bool IsNullable(this Type type) =>
890892
public static MethodInfo GetInvokeMethod(this Type delegateType) => delegateType.GetMethod(invokeMethodName);
891893

892894
/// <summary>
893-
/// Determines whether the specified <paramref name="type"/> inherits the generic <paramref name="baseType"/>.
895+
/// Determines whether the specified <paramref name="type"/> is an ancestor or an instance of the
896+
/// provided <paramref name="openGenericBaseType"/>.
894897
/// </summary>
895898
/// <param name="type">The type to check.</param>
896-
/// <param name="baseType">Type of the generic.</param>
899+
/// <param name="openGenericBaseType">Type of the generic. It is supposed this is an open generic type.</param>
897900
/// <returns>
898-
/// <see langword="true"/> if the specified <paramref name="type"/> inherits the
899-
/// generic <paramref name="baseType"/>; otherwise, <see langword="false"/>.
901+
/// <see langword="true"/> if the specified <paramref name="type"/> is an ancestor or an instance of the
902+
/// provided <paramref name="openGenericBaseType"/>; otherwise, <see langword="false"/>.
900903
/// </returns>
901-
public static bool IsOfGenericType(this Type type, Type baseType) => GetGenericType(type, baseType) != null;
904+
public static bool IsOfGenericType(this Type type, Type openGenericBaseType) =>
905+
GetGenericType(type, openGenericBaseType) != null;
902906

903907
/// <summary>
904-
/// Determines whether the specified <paramref name="type"/> inherits
905-
/// the generic <paramref name="baseGenericTypeDefinition"/> and returns direct inheritor
906-
/// of generic <paramref name="baseGenericTypeDefinition"/> if any.
908+
/// Determines whether the specified <paramref name="type"/> is an ancestor or an instance of
909+
/// the provided <paramref name="openGenericBaseType"/> and returns closed generic type with the
910+
/// specified type arguments if found.
907911
/// </summary>
908912
/// <param name="type">The type to check.</param>
909-
/// <param name="baseGenericTypeDefinition">Type of the generic.</param>
913+
/// <param name="openGenericBaseType">Open generic type to be matched.</param>
910914
/// <returns>
911-
/// Generic <see cref="Type"/> that directly inherits <paramref name="baseGenericTypeDefinition"/> if the
912-
/// specified <paramref name="type"/> inherits the generic <paramref name="baseGenericTypeDefinition"/>;
915+
/// A <see cref="Type"/> representing the closed generic version of <paramref name="openGenericBaseType"/>
916+
/// where type parameters are bound in case it exists in <paramref name="type"/>'s inheritance hierarchy;
913917
/// otherwise, <see langword="null"/>.
914918
/// </returns>
915-
public static Type GetGenericType(this Type type, Type baseGenericTypeDefinition)
919+
public static Type GetGenericType(this Type type, Type openGenericBaseType)
916920
{
917-
var definitionMetadataToken = baseGenericTypeDefinition.MetadataToken;
918-
var definitionModule = baseGenericTypeDefinition.Module;
919-
while (!(type == null || type == ObjectType)) {
920-
if ((type.MetadataToken ^ definitionMetadataToken)==0 && ReferenceEquals(type.Module, definitionModule)) {
921+
var definitionMetadataToken = openGenericBaseType.MetadataToken;
922+
var definitionModule = openGenericBaseType.Module;
923+
while (type != null && !ReferenceEquals(type, ObjectType)) {
924+
if ((type.MetadataToken ^ definitionMetadataToken) == 0 && ReferenceEquals(type.Module, definitionModule)) {
921925
return type;
922926
}
923927

@@ -928,31 +932,49 @@ public static Type GetGenericType(this Type type, Type baseGenericTypeDefinition
928932
}
929933

930934
/// <summary>
931-
/// Determines whether <paramref name="type"/> implements the <paramref name="genericInterface"/>.
935+
/// Determines whether specified <paramref name="type"/> is an implementation of the
936+
/// provided <paramref name="openGenericInterface"/>.
932937
/// </summary>
933-
/// <param name="type">The type.</param>
934-
/// <param name="genericInterface">The <see langword="interface"/>.</param>
938+
/// <param name="type">A <see cref="Type"/> instance to be checked.</param>
939+
/// <param name="openGenericInterface">A <see cref="Type"/> of an open generic <see langword="interface"/>
940+
/// to match the specified <paramref name="type"/> against.</param>
941+
/// <returns>
942+
/// <see langword="true"/> if the specified <paramref name="type"/> is an implementation of the
943+
/// provided <paramref name="openGenericInterface"/>;
944+
/// otherwise, <see langword="false"/>.
945+
/// </returns>
946+
public static bool IsOfGenericInterface(this Type type, Type openGenericInterface) =>
947+
GetGenericInterface(type, openGenericInterface) != null;
948+
949+
/// <summary>
950+
/// Determines whether the specified <paramref name="type"/> is an implementation of the
951+
/// provided <paramref name="openGenericInterface"/> and returns a <see cref="Type"/> instance
952+
/// for the closed generic interface where type arguments are specified if implementation is found.
953+
/// </summary>
954+
/// <param name="type">The type to be checked.</param>
955+
/// <param name="openGenericInterface">Open generic <see langword="interface"/> to be matched.</param>
935956
/// <returns>
936-
/// <see langword="true"/> if the specified <paramref name="type"/> implements the
937-
/// <paramref name="genericInterface"/>; otherwise, <see langword="false"/>.
957+
/// A <see cref="Type"/> representing closed generic version of <paramref name="openGenericInterface"/>
958+
/// where type parameters are bound in case it is implemented by the <paramref name="type"/>;
959+
/// otherwise, <see langword="null"/>.
938960
/// </returns>
939-
public static bool IsOfGenericInterface(this Type type, Type genericInterface)
961+
public static Type GetGenericInterface(this Type type, Type openGenericInterface)
940962
{
941-
var metadataToken = genericInterface.MetadataToken;
942-
var module = genericInterface.Module;
943-
if (type.MetadataToken == metadataToken && ReferenceEquals(type.Module, module)) {
944-
return true;
963+
var metadataToken = openGenericInterface.MetadataToken;
964+
var module = openGenericInterface.Module;
965+
if (type == null || ((type.MetadataToken ^ metadataToken) == 0 && ReferenceEquals(type.Module, module))) {
966+
return type;
945967
}
946968

947969
// We don't use LINQ as we don't want to create a closure here
948970
foreach (var implementedInterface in type.GetInterfaces()) {
949971
if ((implementedInterface.MetadataToken ^ metadataToken) == 0
950972
&& ReferenceEquals(implementedInterface.Module, module)) {
951-
return true;
973+
return implementedInterface;
952974
}
953975
}
954976

955-
return false;
977+
return null;
956978
}
957979

958980
/// <summary>

0 commit comments

Comments
 (0)