Skip to content

Commit e18613e

Browse files
authored
Merge pull request castleproject#645 from stakx/refactor/method-finder
Refactor `MethodFinder.GetAllInstanceMethods`
2 parents 7e062ea + a797811 commit e18613e

File tree

7 files changed

+102
-167
lines changed

7 files changed

+102
-167
lines changed

src/Castle.Core.Tests/DynamicProxy.Tests/GenericInterfaceProxyTestCase.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ namespace Castle.DynamicProxy.Tests
1616
{
1717
using System;
1818
using System.Collections.Generic;
19-
using System.Reflection;
2019

21-
using Castle.DynamicProxy.Generators;
20+
using Castle.DynamicProxy.Internal;
2221
using Castle.DynamicProxy.Tests.GenInterfaces;
2322
using Castle.DynamicProxy.Tests.Interceptors;
2423
using Castle.DynamicProxy.Tests.Interfaces;
@@ -415,17 +414,17 @@ public void TypeGetMethodsIsStable()
415414
[Test(Description =
416415
"There is a strange CLR bug resulting from our loading the tokens of methods in generic types. " +
417416
"This test ensures we correctly work around it.")]
418-
public void MethodFinderIsStable()
417+
public void GetAllInstanceMethodsIsStable()
419418
{
420419
ProxyWithGenInterfaceWithBase();
421-
Assert.AreEqual(4, MethodFinder.GetAllInstanceMethods(
422-
typeof(IGenInterfaceHierarchyBase<int>), BindingFlags.Public | BindingFlags.Instance).Length);
420+
Assert.AreEqual(4, typeof(IGenInterfaceHierarchyBase<int>).GetAllInstanceMethods().Length);
423421
}
424422

425423
#if FEATURE_APPDOMAIN
426424
[Test(Description =
427425
"There is a strange CLR bug resulting from our loading the tokens of methods in generic types. " +
428-
"This test ensures we do not trigger it across AppDomains. If we do, MethodFinder must provide a cross-AppDomain workaround.")]
426+
"This test ensures we do not trigger it across AppDomains. " +
427+
"If we do, GetAllInstanceMethods must provide a cross-AppDomain workaround.")]
429428
public void TypeGetMethodsIsStableInDifferentAppDomains()
430429
{
431430
ProxyWithGenInterfaceWithBase();

src/Castle.Core.Tests/DynamicProxy.Tests/MethodFinderTestCase.cs

Lines changed: 0 additions & 76 deletions
This file was deleted.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2004-2021 Castle Project - http://www.castleproject.org/
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Castle.DynamicProxy.Tests
16+
{
17+
using System.Linq;
18+
using System.Reflection;
19+
20+
using Castle.DynamicProxy.Internal;
21+
22+
using NUnit.Framework;
23+
24+
[TestFixture]
25+
public class TypeUtilTestCase
26+
{
27+
[Test]
28+
public void GetAllInstanceMethods_GetsPublicAndNonPublicMethods()
29+
{
30+
MethodInfo[] methods =
31+
typeof(object).GetAllInstanceMethods();
32+
MethodInfo[] realMethods =
33+
typeof(object).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
34+
CollectionAssert.AreEquivalent(realMethods, methods);
35+
}
36+
37+
// The test above suggests that `TypeUtil.GetAllInstanceMethods` and `Type.GetMethods`
38+
// can be used interchangeably, but this is not always the case. See the test(s) below and
39+
// `GenericInterfaceProxyTestCase.GetAllInstanceMethodsIsStable` for cases where the two methods
40+
// may produce different results.
41+
42+
#if NET5_0_OR_GREATER
43+
44+
[Test]
45+
public void GetAllInstanceMethods_NoDuplicatesForMethodWithCovariantReturnType()
46+
{
47+
MethodInfo[] methods =
48+
typeof(Derived).GetAllInstanceMethods();
49+
MethodInfo[] realMethods =
50+
typeof(Derived).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
51+
CollectionAssert.AreNotEquivalent(realMethods, methods);
52+
CollectionAssert.IsSubsetOf(subset: methods, superset: realMethods);
53+
Assert.AreEqual(2, realMethods.Where(m => m.Name == nameof(Derived.Method)).Count());
54+
Assert.AreEqual(1, methods.Where(m => m.Name == nameof(Derived.Method)).Count());
55+
}
56+
57+
public abstract class Base
58+
{
59+
public abstract Base Method();
60+
}
61+
62+
public abstract class Derived : Base
63+
{
64+
public override abstract Derived Method();
65+
}
66+
67+
#endif
68+
}
69+
}

src/Castle.Core/DynamicProxy/Contributors/MembersCollector.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ void CollectEvents()
7272

7373
void CollectMethods()
7474
{
75-
var methodsFound = MethodFinder.GetAllInstanceMethods(type, Flags);
75+
var methodsFound = type.GetAllInstanceMethods();
7676
foreach (var method in methodsFound)
7777
{
7878
AddMethod(method, true);

src/Castle.Core/DynamicProxy/Generators/MethodFinder.cs

Lines changed: 0 additions & 83 deletions
This file was deleted.

src/Castle.Core/DynamicProxy/Internal/InvocationHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private static MethodInfo ObtainMethod(MethodInfo proxiedMethod, Type type)
7373
else
7474
{
7575
// NOTE: this implementation sucks, feel free to improve it.
76-
var methods = MethodFinder.GetAllInstanceMethods(type, BindingFlags.Public | BindingFlags.NonPublic);
76+
var methods = type.GetAllInstanceMethods();
7777
foreach (var method in methods)
7878
{
7979
if (MethodSignatureComparer.Instance.Equals(method.GetBaseDefinition(), proxiedMethod))

src/Castle.Core/DynamicProxy/Internal/TypeUtil.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ namespace Castle.DynamicProxy.Internal
1717
using System;
1818
using System.Collections.Generic;
1919
using System.Diagnostics;
20+
using System.Linq;
2021
using System.Reflection;
2122
using System.Reflection.Emit;
2223

24+
using Castle.DynamicProxy.Generators;
2325
using Castle.DynamicProxy.Generators.Emitters;
2426

2527
public static class TypeUtil
2628
{
29+
private static readonly Dictionary<Type, MethodInfo[]> instanceMethodsCache = new Dictionary<Type, MethodInfo[]>();
30+
2731
internal static bool IsNullableType(this Type type)
2832
{
2933
return type.IsGenericType &&
@@ -55,6 +59,28 @@ internal static FieldInfo[] GetAllFields(this Type type)
5559
return fields.ToArray();
5660
}
5761

62+
/// <summary>
63+
/// Returns the methods implemented by a type. Use this instead of Type.GetMethods to filter out duplicate MethodInfos
64+
/// sometimes reported by the latter. The test suite documents cases where such duplicates may occur.
65+
/// </summary>
66+
internal static MethodInfo[] GetAllInstanceMethods(this Type type)
67+
{
68+
MethodInfo[] methodsInCache;
69+
70+
lock (instanceMethodsCache)
71+
{
72+
if (!instanceMethodsCache.TryGetValue(type, out methodsInCache))
73+
{
74+
methodsInCache = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
75+
.Distinct(MethodSignatureComparer.Instance)
76+
.ToArray();
77+
instanceMethodsCache.Add(type, methodsInCache);
78+
}
79+
}
80+
81+
return methodsInCache;
82+
}
83+
5884
/// <summary>
5985
/// Returns list of all unique interfaces implemented given types, including their base interfaces.
6086
/// </summary>

0 commit comments

Comments
 (0)