Як визначити, чи реалізує тип інтерфейс із відображенням C #


561

Чи пропонує відображення в C#запропонованому способі визначити, чи є в деяких System.Typeмоделях даного типу якийсь інтерфейс?

public interface IMyInterface {}

public class MyType : IMyInterface {}

// should yield 'true'
typeof(MyType)./* ????? */MODELS_INTERFACE(IMyInterface);

Відповіді:


968

У вас є кілька варіантів:

  1. typeof(IMyInterface).IsAssignableFrom(typeof(MyType))

  2. typeof(MyType).GetInterfaces().Contains(typeof(IMyInterface))

Для загального інтерфейсу це трохи інакше.

typeof(MyType).GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMyInterface<>))

68
Пам’ятайте, що typeof (IMyInterface) .IsAssignableFrom (typeof (IMyInterface)) також є істинним, що може спричинити несподіваний результат у вашому коді.
Кріс Кемп

29
Це, звичайно, було легко не звертати уваги і отримувати аргументи для IsAssignableFromзворотного руху. Я піду GetInterfacesзараз: p
Бенджамін

12
IsAssignableFrom(t1)Варіант приблизно в 3 рази швидше , ніж GetInterfaces().Contains(t2)аналог в моєму коді.
П’єр Арно

24
@PierreArnaud: IsAssignableFrom зрештою викликає GetInterfaces, тому, ймовірно, ваш тест перевірив спочатку GetInterfaces та IsAssignable після. Це тому, що GetInterfaces кешує результати, тому перше виклик коштує дорожче
Panos Theof

17
Невелика зміна відповіді @ Коста. За допомогою C # 6 ми можемо зробити typeof(MyType).GetInterface(nameof(IMyInterface)) != nullкращу безпеку та рефакторинг.
aholmes


32
typeof(IMyInterface).IsAssignableFrom(someclass.GetType());

або

typeof(IMyInterface).IsAssignableFrom(typeof(MyType));

34
Якщо у вас вже є примірник класу, набагато кращий підхід - це просто someclass is IMyInterfaceтакий, який зовсім не передбачає витрат на роздуми. Тож, не помиляючись, це не ідеальний спосіб зробити це.
Джеймс Дж. Реган IV

1
@James - погоджуюсь. Навіть Решарпер дає ту саму пропозицію.
Ангшуман Агарвал

@ JamesJ.ReganIV Ви повинні опублікувати це як відповідь, я майже не пропустив ваш коментар
reggaeguitar

@reggaeguitar, дякую, але коментар не відповідає на початкове запитання. Питання задає рішення Reflection, я просто кажу в першому випадку цієї відповіді, коли у вас є екземпляр відображення об'єкта, не є ідеальним рішенням.
Джеймс Дж. Реган IV

1
@ JamesJ.ReganIV Власне, isперевіряє в обох напрямках ієрархії спадкування, тоді як IsAssignableFromперевіряє лише вгору. Крім того, якщо у вас є екземпляр об'єкта, вам слід зателефонувати IsInstanceOfType(який також дивиться лише вгору).
Sellorio

13
public static bool ImplementsInterface(this Type type, Type ifaceType) 
{
    Type[] intf = type.GetInterfaces();
    for(int i = 0; i < intf.Length; i++) 
    {
        if(intf[ i ] == ifaceType) 
        {
            return true;
        }
    }
    return false;
}

Я думаю, що це правильний випуск з трьох причин:

  1. Він використовує GetInterfaces, а не IsAssignableFrom, але це швидше, оскільки IsAssignableFrom врешті-решт після декількох перевірок викликає GetInterfaces.
  2. Він повторює локальний масив, тому перевірки меж не буде.
  3. Він використовує оператор ==, який визначений для типу, тому, ймовірно, безпечніший, ніж метод «Рівний» (який з часом містить виклик).

10
+1 за вміст, я ненавиджу пробіли навколо парен та єгипетських брекетів. Також весь метод можна записати як: return type.GetInterfaces (). Будь-який (t => t == ifaceType);
reggaeguitar

1
Inter.ly type.IsAssignableFrom () діє точно так само, як і ваш код
відхилення

1
Крім того, чому б не ввести.GetInterfaces (). Містить (ifaceType), який не використовує LINQ.

9

Я тільки що зробив:

public static bool Implements<I>(this Type source) where I : class
{
  return typeof(I).IsAssignableFrom(source);
}

Я б хотів сказати where I : interface, але interfaceце не є загальним параметром обмеження параметрів. classє настільки ж близьким, як стає.

Використання:

if(MyType.Implements<IInitializable>())
  MyCollection.Initialize();

Я щойно сказав, Implementsтому що це більш інтуїтивно. Я завжди IsAssignableFromперевертаюсь.


Ви можете зробити, return typeof(I).IsInterface && typeof(I).IsAssignableFrom(source);щоб повернути помилкове на будь-яких "неправильних" звичках методу, тобто; використовуючи його з типом класу замість типу інтерфейсу, альтернативно кидайте виняток, якщо параметр type не є інтерфейсом. Хоча ви можете стверджувати, що похідний клас 'реалізує' його батьків ...
Sindri Jóelsson

7

Модифікація відповіді Джеффа для оптимальної роботи (завдяки тесту на продуктивність П'єра Арно):

var type = typeof(MyType);
var implementsInterface = typeof(IMyInterface).IsAssignableFrom(type) && type.IsClass;

Щоб знайти всі типи, які реалізують інтерфейс у заданому Assembly:

var implementations = typeof(TypeInTargetAssembly).Assembly.GetTypes()
                          .Where(t => typeof(IMyInterface).IsAssignableFrom(t) && t.IsClass);

7

Як уже хтось згадував: Бенджамін 10 квітня 1313 о 22:21 "

Напевно, було легко не звернути уваги і отримати аргументи для IsAssignableFrom назад. Я перейду з GetInterfaces зараз: p -

Ну, інший спосіб - це просто створити метод короткого розширення, який певною мірою виконує "найбільш звичайний" спосіб мислення (і погодився, це дуже малий особистий вибір, щоб зробити його трохи "більш природним", виходячи з своїх уподобань ):

public static class TypeExtensions
{
    public static bool IsAssignableTo(this Type type, Type assignableType)
    {
        return assignableType.IsAssignableFrom(type);
    }
}

І чому б не піти трохи більш загально (ну не впевнений, чи справді це так цікаво, я припускаю, що я просто передаю ще одну щіпку «синтаксису» цукру):

public static class TypeExtensions
{
    public static bool IsAssignableTo(this Type type, Type assignableType)
    {
        return assignableType.IsAssignableFrom(type);
    }

    public static bool IsAssignableTo<TAssignable>(this Type type)
    {
        return IsAssignableTo(type, typeof(TAssignable));
    }
}

Я думаю, що це може бути набагато природніше, але ще раз лише питання дуже особистої думки:

var isTrue = michelleType.IsAssignableTo<IMaBelle>();

4
Чи є причина, що ви не просто поставили реалізацію безпосередньо в метод розширення? Я маю на увазі впевнений, що це дозволяє називати це обома способами, але навіщо вам це коли-небудь потрібно робити?
Марк А. Донохое

@MarqueIV Вибачте, що повернувся до вас майже на 2 роки пізніше, ну, мабуть, це було давньою шкідливою звичкою, щоб потім обернути хелперний метод у спосіб розширення, щоб уникнути повторення коду, відредагуйте мою відповідь :)
Керрі Перрет

1
@MarqueIV зробив плюс змінив мою іншу шкідливу звичку не використовувати псевдонім, тобто Boolean=> bool(я не знаю, чому я колись був молодим).
Керрі Перрет

3

Якщо у вас є тип або екземпляр, ви можете легко перевірити, чи підтримують вони певний інтерфейс.

Щоб перевірити, чи об’єкт реалізує певний інтерфейс:

if(myObject is IMyInterface) {
  // object myObject implements IMyInterface
}

Щоб перевірити, чи тип реалізує певний інтерфейс:

if(typeof(IMyInterface).IsAssignableFrom(typeof(MyType))) {
  // type MyType implements IMyInterface
}

Якщо ви отримали загальний об'єкт і хочете зробити акторський склад, а також перевірити, чи реалізований інтерфейс, який ви передаєте, реалізовано код:

 var myCastedObject = myObject as IMyInterface;

    if(myCastedObject != null) {
      // object myObject implements IMyInterface
    }


1

Кожен, хто шукає це, може вважати корисним наступний метод розширення:

public static class TypeExtensions
{
    public static bool ImplementsInterface(this Type type, Type @interface)
    {
        if (type == null)
        {
            throw new ArgumentNullException(nameof(type));
        }

        if (@interface == null)
        {
            throw new ArgumentNullException(nameof(@interface));
        }

        var interfaces = type.GetInterfaces();
        if (@interface.IsGenericTypeDefinition)
        {
            foreach (var item in interfaces)
            {
                if (item.IsConstructedGenericType && item.GetGenericTypeDefinition() == @interface)
                {
                    return true;
                }
            }
        }
        else
        {
            foreach (var item in interfaces)
            {
                if (item == @interface)
                {
                    return true;
                }
            }
        }

        return false;
    }
}

тести xunit:

public class TypeExtensionTests
{
    [Theory]
    [InlineData(typeof(string), typeof(IList<int>), false)]
    [InlineData(typeof(List<>), typeof(IList<int>), false)]
    [InlineData(typeof(List<>), typeof(IList<>), true)]
    [InlineData(typeof(List<int>), typeof(IList<>), true)]
    [InlineData(typeof(List<int>), typeof(IList<int>), true)]
    [InlineData(typeof(List<int>), typeof(IList<string>), false)]
    public void ValidateTypeImplementsInterface(Type type, Type @interface, bool expect)
    {
        var output = type.ImplementsInterface(@interface);
        Assert.Equal(expect, output);
    }
}

0

а як на рахунок

if(MyType as IMyInterface != null)

?


4
Це очевидно, коли у мене є екземпляр. Не корисно, коли у мене є тип відбиття
edc65


0

Правильна відповідь

typeof(MyType).GetInterface(nameof(IMyInterface)) != null;

Однак,

typeof(MyType).IsAssignableFrom(typeof(IMyInterface));

може повернути неправильний результат, як показано наступним кодом з рядком та IConvertible:

    static void TestIConvertible()
    {
        string test = "test";
        Type stringType = typeof(string); // or test.GetType();

        bool isConvertibleDirect = test is IConvertible;
        bool isConvertibleTypeAssignable = stringType.IsAssignableFrom(typeof(IConvertible));
        bool isConvertibleHasInterface = stringType.GetInterface(nameof(IConvertible)) != null;

        Console.WriteLine($"isConvertibleDirect: {isConvertibleDirect}");
        Console.WriteLine($"isConvertibleTypeAssignable: {isConvertibleTypeAssignable}");
        Console.WriteLine($"isConvertibleHasInterface: {isConvertibleHasInterface}");
    }

Результати:

 isConvertibleDirect: True
 isConvertibleTypeAssignable: False
 isConvertibleHasInterface: True

4
Як видно з прийнятої відповіді, ви змінили типи використання IsAssignableFrom. Так само, як про це попереджають Бенджамін і Еуарн.
VV5198722

0

Зауважте, що якщо у вас є загальний інтерфейс, IMyInterface<T>він завжди повертається false:

  typeof(IMyInterface<>).IsAssignableFrom(typeof(MyType)) /* ALWAYS FALSE */

Це також не працює:

  typeof(MyType).GetInterfaces().Contains(typeof(IMyInterface<>))  /* ALWAYS FALSE */

Однак, якщо MyTypeвпроваджує IMyInterface<MyType>це, працює і повертає true:

  typeof(IMyInterface<MyType>).IsAssignableFrom(typeof(MyType))

Однак ви, ймовірно, не будете знати параметр типу Tпід час виконання . Дещо хакітним рішенням є:

  typeof(MyType).GetInterfaces()
                .Any(x=>x.Name == typeof(IMyInterface<>).Name)

Рішення Джеффа трохи менше хакі:

  typeof(MyType).GetInterfaces()
         .Any(i => i.IsGenericType 
             && i.GetGenericTypeDefinition() == typeof(IMyInterface<>));

Ось метод розширення, Typeякий працює в будь-якому випадку:

public static class TypeExtensions
{
    public static bool IsImplementing(this Type type, Type someInterface)
    {
        return type.GetInterfaces()
             .Any(i => i == someInterface 
                 || i.IsGenericType 
                    && i.GetGenericTypeDefinition() == someInterface);
    }
}

(Зверніть увагу, що вище використовується linq, який, ймовірно, повільніше, ніж цикл.)

Потім ви можете зробити:

   typeof(MyType).IsImplementing(IMyInterface<>)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.