Обновленный вопрос, данный корректный ответ Andrew Hare:
Учитывая следующие классы C#:
public class Bar : Foo, IDisposable
{
// implementation of Bar and IDisposable
}
public class Foo : IEnumerable<int>
{
// implementation of Foo and all its inherited interfaces
}
Я хочу метод как следующий, который не перестал работать на утверждениях (Примечание: Вы не можете изменить утверждения):
public void SomeMethod()
{
// This doesn't work
Type[] interfaces = typeof(Bar).GetInterfaces();
Debug.Assert(interfaces != null);
Debug.Assert(interfaces.Length == 1);
Debug.Assert(interfaces[0] == typeof(IDisposable));
}
Кто-то может помочь путем фиксации этого метода, таким образом, утверждения не перестали работать?
Вызов typeof(Bar).GetInterfaces()
не работает, потому что это возвращает всю интерфейсную иерархию (т.е. interfaces
переменная содержит IEnumerable<int>
, IEnumerable
, и IDisposable
), не только верхний уровень.
Попробуйте следующее:
using System.Linq;
public static class Extensions
{
public static Type[] GetTopLevelInterfaces(this Type t)
{
Type[] allInterfaces = t.GetInterfaces();
var selection = allInterfaces
.Where(x => !allInterfaces.Any(y => y.GetInterfaces().Contains(x)))
.Except(t.BaseType.GetInterfaces());
return selection.ToArray();
}
}
использование:
private void Check(Type t, Type i)
{
var interfaces = t.GetTopLevelInterfaces();
Debug.Assert(interfaces != null, "interfaces is null");
Debug.Assert(interfaces.Length == 1, "length is not 1");
Debug.Assert(interfaces[0] == i, "the expected interface was not found");
System.Console.WriteLine("\n{0}", t.ToString());
foreach (var intf in interfaces)
System.Console.WriteLine(" " + intf.ToString());
}
public void Run()
{
Check(typeof(Foo), typeof(IEnumerable<int>));
Check(typeof(Bar), typeof(IDisposable));
}
Как отмечалось в другом месте, это работает, только если проверяемый тип явно реализует единственный интерфейс. Если у вас их несколько, вам нужно изменить свой Assert.
Примечание. Обновлено также для фильтрации унаследованных интерфейсов.
Вы можете исключить элементы базового интерфейса, например:
public Type[] GetDeclaredInterfaces( Type type )
{
if( type == typeof(object) )
return new Type[ 0 ];
Type[] interfaces = type.GetInterfaces();
Type[] baseInterfaces = interfaces.Where( i => i.BaseType != null && i.BaseType.IsInterface );
Type[] declaredInterfaces = interfaces.Except( type.BaseType.GetInterfaces() );
return declaredInterfaces.Except( baseInterfaces );
}
Эндрю Хэйр прав в том, что вы не можете получить указанный список интерфейсов с помощью отражения. Однако вы можете найти интерфейсы «верхнего уровня», исключив любые интерфейсы, подразумеваемые другими. Вы можете реализовать это так:
Type[] allInterfaces = typeof(Foo).GetInterfaces();
Type[] interfaces = allInterfaces
.Where(x => !allInterfaces.Any(y => y.GetInterfaces().Contains(x)))
.ToArray();
Это передает ваши утверждения.
Вы просто хотите получить интерфейсы первого уровня, верно? Вы можете смешать LINQ и рефлексию; просто исключите все, что реализует базовый тип.
var fooType = typeof(Foo);
if(fooType.BaseType == null)
return fooType.GetInterfaces().ToArray();
return fooType
.GetInterfaces()
.Except(fooType.BaseType.GetInterfaces())
.ToArray();
На самом деле нет никакого способа сделать это, поскольку вы получаете все интерфейсы из иерархии интерфейсов. Это означает, что когда вы реализуете IEnumerable
, вы также неявно реализуете IEnumerable
.
Другими словами, если вы посмотрите на IL для созданного вами класса, вы увидите следующее:
.class public auto ansi beforefieldinit Foo
extends [mscorlib]System.Object
implements [mscorlib]System.Collections.Generic.IEnumerable`1<int32>,
[mscorlib]System.Collections.IEnumerable
{
// ...
}
Несмотря на то, что вы только указали, что ваш тип реализует IEnumerable
, компилятор имеет испущенный IL, который указывает, что ваш тип реализует IEnumerable
и IEnumerable
.
API отражения с радостью возвращает то, что вы фактически определили для типа (а именно, что ваш тип реализует оба интерфейса - что он и делает). Компилятор C # позволяет вам ссылаться только на самый нижний тип в иерархии интерфейсов, поскольку он будет заполнять другие интерфейсы, которые также реализует ваш тип. Это один из отличий наследования интерфейсов от наследования типов.
Я бы написал это так:
public void SomeMethod()
{
Type[] interfaces = typeof(Foo).GetInterfaces();
Debug.Assert(interfaces.Contains(typeof(IEnumerable<int>)));
}
Но трудно ответить, не зная, что вы пытаетесь проверить. Тем не менее, вы не должны полагаться на порядок при использовании GetInterfaces
, и этот метод вернет пустой массив, если тип не реализует его, поэтому проверка на null не требуется.
Изменить: если вы действительно не можете изменить утверждения, безопаснее всего сделать следующее:
Type[] allInterfaces = typeof(Foo).GetInterfaces();
var interfaces = allInterfaces.Where(x => x == typeof(IEnumerable<int>)).ToArray();
Debug.Assert(interfaces != null);
Debug.Assert(interfaces.Length == 1);
Debug.Assert(interfaces[0] == typeof(IEnumerable<int>));