Як визначити, чи реалізує тип інтерфейс із відображенням 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.