我正在尝试使用反射来检索接口及其基本接口的所有方法的列表。
到目前为止,我有这个:
var methods = type.GetMethods().Concat(
type.GetInterfaces()
.SelectMany(@interface => @interface.GetMethods()));
我希望能够过滤掉在基本接口中声明的阴影方法的方法,即"新"方法:
public interface IBaseInterface
{
string Method();
}
public interface IInterfaceWithNewMethod : IBaseInterface
{
new string Method();
}
对于我当前的代码,结果包括这两个方法——我只想检索IInterfaceWithMethod.Method
并过滤掉IBaseInterface.Method
。
Fiddle:https://dotnetfiddle.net/fwVeLS
PS:如果有帮助,您可以假设我可以访问派生接口的具体实例。该实例的类型只有在运行时才知道(它是一个动态代理)。
好吧,一种糟糕的方法可能是手动检查方法签名是否匹配。
检查签名的方法可能如下所示:
public static bool HasSameSignature(MethodInfo potentiallyHidingMethod, MethodInfo baseMethod)
{
//different name, therefore not same signature
if (potentiallyHidingMethod.Name != baseMethod.Name)
return false;
//now we check if they have the same parameter types...
var potentiallyHidingMethodParameters = potentiallyHidingMethod.GetParameters();
var baseMethodParameters = baseMethod.GetParameters();
//different number of parameters, therefore not same signature
if (potentiallyHidingMethodParameters.Length != baseMethodParameters.Length)
return false;
for (int i = 0; i < potentiallyHidingMethodParameters.Length; i++)
{
//if a parameter type doesn't match, it's not the same signature
if (potentiallyHidingMethodParameters[i].ParameterType != baseMethodParameters[i].ParameterType)
return false;
}
//if we've gotten this far, they have the same name and parameters,
//therefore, it's the same signature.
return true;
}
然后是检查派生接口方法的问题,看看它们是否隐藏(或匹配)任何基本接口方法:
Type type = typeof(IInterfaceWithNewMethod);
var potentiallyHidingMethods = type.GetMethods();
var baseTypeMethods =type.GetInterfaces()
.SelectMany(@interface => @interface.GetMethods());
var hidingMethods = potentiallyHidingMethods
.Where(hiding => baseTypeMethods.Any(baseMethod => HasSameSignature(hiding, baseMethod)));
注意,这是一个有点天真的实现。如果有一种更简单的方法或角落案例没有涵盖,我不会感到惊讶。
编辑:有点误解了所需的输出。使用上面的代码,这将为您提供所有的基本接口方法,加上派生接口方法,但过滤掉了被派生接口隐藏的任何基本接口方法:
var allMethodsButFavouringHiding = potentiallyHidingMethods.Concat(
baseTypeMethods.Where(baseMethod => !potentiallyHidingMethods.Any(potentiallyhiding => HasSameSignature(potentiallyhiding, baseMethod))));
EDITx2:我做了一个测试,给出了以下接口:
public interface IBaseInterface
{
string BaseMethodTokeep();
string MethodToHide();
string MethodSameName();
}
public interface IInterfaceWithNewMethod : IBaseInterface
{
new string MethodToHide();
new string MethodSameName(object butDifferentParameters);
string DerivedMethodToKeep();
}
结果是MethodInfo
:的集合
MethodToHide (IInterfaceWithNewMethod)
MethodSameName (IInterfaceWithNewMethod)
DerivedMethodToKeep (IInterfaceWithNewMethod)
BaseMethodTokeep (IBaseInterface)
MethodSameName (IBaseInterface)
因此,它保留了任何未隐藏的基本接口方法,任何派生接口方法(隐藏或其他),并接受任何签名更改(即,导致不隐藏的不同参数)。
EDITx3:添加了另一个带有过载的测试:
public interface IBaseInterface
{
string MethodOverloadTest();
string MethodOverloadTest(object withParam);
}
public interface IInterfaceWithNewMethod : IBaseInterface
{
new string MethodOverloadTest();
}
结果为:
MethodOverloadTest() for IInterfaceWithNewMethod
MethodOverloadTest(object) for IBaseInterface
我最终使用了Chris Sinclair和Thomas Levsque的混合答案。
它更广泛一些,但更健壮。
更重要的是,我认为它更容易阅读和推理,这是处理反思时的首要任务。我们都知道反射代码变得复杂和混乱是多么容易。。。
internal static class TypeExtensions
{
/// <summary>
/// Gets a collection of all methods declared by the interface <paramref name="type"/> or any of its base interfaces.
/// </summary>
/// <param name="type">An interface type.</param>
/// <returns>A collection of all methods declared by the interface <paramref name="type"/> or any of its base interfaces.</returns>
public static IEnumerable<MethodInfo> GetInterfaceMethods(this Type type)
{
var allMethods = type.GetMethods().Concat(
type.GetInterfaces()
.SelectMany(@interface => @interface.GetMethods()));
return allMethods.GroupBy(method => new Signature(method))
.Select(SignatureWithTheMostDerivedDeclaringType);
}
private static MethodInfo SignatureWithTheMostDerivedDeclaringType(IGrouping<Signature, MethodInfo> group)
{
return group.Aggregate(
(a, b) => a.DeclaringType.IsAssignableFrom(b.DeclaringType) ? b : a);
}
private sealed class Signature
{
private readonly MethodInfo method;
public Signature(MethodInfo method)
{
this.method = method;
}
public override bool Equals(object obj)
{
var that = obj as Signature;
if (that == null)
return false;
//different names, therefore different signatures.
if (this.method.Name != that.method.Name)
return false;
var thisParams = this.method.GetParameters();
var thatParams = that.method.GetParameters();
//different number of parameters, therefore different signatures
if (thisParams.Length != thatParams.Length)
return false;
//different paramaters, therefore different signatures
for (int i = 0; i < thisParams.Length; i++)
if (!AreParamsEqual(thisParams[i], thatParams[i]))
return false;
return true;
}
/// <summary>
/// Two parameters are equal if they have the same type and
/// they're either both "out" parameters or "non-out" parameters.
/// </summary>
private bool AreParamsEqual(ParameterInfo x, ParameterInfo y)
{
return x.ParameterType == y.ParameterType &&
x.IsOut == y.IsOut;
}
public override int GetHashCode()
{
int hash = 37;
hash = hash*23 + method.Name.GetHashCode();
foreach (var p in method.GetParameters())
{
hash = hash*23 + p.ParameterType.GetHashCode();
hash = hash*23 + p.IsOut.GetHashCode();
}
return hash;
}
}
}