interface 型の Type.GetMembers や GetMethods では継承元は無視される

ref: Type.GetMethods Method (BindingFlags)

具体的にどういう事かは ref 先のユーザーフィードバックのコメントに詳しいが、要するに interface の Type に対して GetMembers などを呼んだ場合、基底 interface のメンバーは列挙されないということである。

したがって、interface の全メンバーなりメソッドなりを列挙するつもりで GetMembers や GetMethods を使うと、派生元になっている interface のメンバーが出てこなくなって悲しいことになる。のでこのようなエントリを書くに至った次第*1

対処法としては、ref 先にもあるように GetInterfaces() を使って、祖先インターフェースたちのメンバーを再帰的に列挙する他にないだろう。

なお、ref 先では HashSet を使って重複を弾いているが、mono の MethodInfo 実装では GetHashCode や Equals をオーバーライドしていない*2ため、mono 環境においては HashSet では重複をはじけない可能性が十分にある。

そのため、mono 環境でも動く、ランタイムの実装に依存しないコードにするためには、再帰的列挙の過程で過去に列挙済みの interface の型を覚えておくようにし、そもそも同一 interface は必ず一回だけ列挙するようにすれば良いだろう。

具体的には、以下のようなコードが上記を踏まえた workaround になるだろう。

/// <summary>
/// 
/// </summary>
[ThreadStatic]
private static Dictionary<KeyValuePair<Type, BindingFlags>, MethodInfo[]> allMethods;
/// <summary>
/// Returns all <see cref="MethodInfo"/>s of given <paramref name="type"/>. 
/// Even if given type is interface, this method returns all methods (unlike type.GetMethods).
/// </summary>
/// <param name="type"></param>
/// <param name="flags"></param>
/// <returns></returns>
public static IEnumerable<MethodInfo> GetAllMethods(this Type type, BindingFlags flags) {
	MethodInfo[] methods;
	var key = new KeyValuePair<Type, BindingFlags>(type, flags);
	if (allMethods == null) allMethods = new Dictionary<KeyValuePair<Type, BindingFlags>, MethodInfo[]>();
	if (!allMethods.TryGetValue(key, out methods)) {
		allMethods[key] = methods = (type.IsInterface) 
			? type.GetAllMethodsOfInterface(flags).ToArray() 
			: type.GetMethods(flags);
	}
	return methods;
}
/// <summary>
/// 
/// </summary>
/// <param name="interfaceType"></param>
/// <param name="flags"></param>
/// <returns></returns>
private static IEnumerable<MethodInfo> GetAllMethodsOfInterface(this Type interfaceType, BindingFlags flags) {
	if (!interfaceType.IsInterface) throw new ArgumentException("Given type isn't interface: " + interfaceType.Name);
	return interfaceType.GetAllMethodsOfInterface(flags, new HashSet<Type>());
}
/// <summary>
/// 
/// </summary>
/// <param name="interfaceType"></param>
/// <param name="flags"></param>
/// <param name="visitedInterfaces"></param>
/// <returns></returns>
private static IEnumerable<MethodInfo> GetAllMethodsOfInterface(this Type interfaceType, BindingFlags flags, HashSet<Type> visitedInterfaces) {
	if (!interfaceType.IsInterface) throw new ArgumentException("Given type isn't interface: " + interfaceType.Name);
	if (!visitedInterfaces.Add(interfaceType)) return Enumerable.Empty<MethodInfo>();
	return interfaceType.GetMethods(flags).Concat(
		interfaceType.GetInterfaces().SelectMany((p) => p.GetAllMethodsOfInterface(flags, visitedInterfaces))
	);
}

*1:BindingFlags の指定が間違っているのかと思って回り道してしまった・・・。

*2:.net の場合は MethodInfo 実装型でオーバーライドしているが、public な型である MethodInfo の規約としては別にそうではない。