Як визначити, чи тип реалізує певний загальний тип інтерфейсу


226

Припустимо такі визначення типів:

public interface IFoo<T> : IBar<T> {}
public class Foo<T> : IFoo<T> {}

Як дізнатись, чи реалізує тип Fooзагальний інтерфейс, IBar<T>коли доступний лише керований тип?

Відповіді:


387

Використовуючи відповідь від TcKs, це також можна зробити за допомогою наступного запиту LINQ:

bool isBar = foo.GetType().GetInterfaces().Any(x =>
  x.IsGenericType &&
  x.GetGenericTypeDefinition() == typeof(IBar<>));

1
Це дуже елегантне рішення! Інші, яких я бачив на SO, використовують петлі foreach або довші запити LINQ. Майте на увазі, що для його використання вам потрібно мати .NET Framework 3.5.
Даніель Т.

7
Я рекомендую зробити цей метод розширення a la bit.ly/ccza8B - очистить це досить добре!
Бред Хеллер

1
Залежно від ваших потреб, вам може знадобитися повторне повернення інтерфейсів, що повертаються.
Себастьян Добрий

4
Я б сказав, що це повинно бути реалізовано в .net набагато краще ... як ядро ​​... як member.Implements (IBar) або CustomType.Implements (IBar), а ще краще, використовуючи ключове слово "є" .. .. Я досліджую c #, і я вже трохи більше розчарувався в .net зараз ...
Sofija

2
незначне доповнення: якщо IBar має декілька родових типів, вам потрібно вказати це так: typeof(IBar<,,,>)комами, що діють як заповнювачі
Rob Von Nesselrode

33

Вам потрібно пройти по дереву спадкування і знайти всі інтерфейси для кожного класу на дереві та порівняти typeof(IBar<>) з результатом виклику, Type.GetGenericTypeDefinition якщо інтерфейс є загальним. Це, звичайно, трохи боляче.

Дивіться цю відповідь та ці для отримання додаткової інформації та коду.


чому б просто не передати на IBar <SomeClass> і перевірити наявність нуля? (Я маю на увазі кастинг із "як" звичайно)
Пабло Ретик

5
T невідомий і не може бути призначений для конкретного типу.
sduplooy

@sduplooy: можливо, я пропускаю щось, як я можу бути невідомим? вона складе публічний клас Foo: IFoo <T> {}
Пабло Ретик

25
public interface IFoo<T> : IBar<T> {}
public class Foo : IFoo<Foo> {}

var implementedInterfaces = typeof( Foo ).GetInterfaces();
foreach( var interfaceType in implementedInterfaces ) {
    if ( false == interfaceType.IsGeneric ) { continue; }
    var genericType = interfaceType.GetGenericTypeDefinition();
    if ( genericType == typeof( IFoo<> ) ) {
        // do something !
        break;
    }
}

2
Оскільки typeof (Foo) повертає об'єкт System.Type (описуючи Foo), виклик GetType () завжди поверне тип для System.Type. Ви повинні змінити на typeof (Foo) .GetInterfaces ()
Майкл Медоуз

9

Як допоміжний метод розширення

public static bool Implements<I>(this Type type, I @interface) where I : class
{
    if(((@interface as Type)==null) || !(@interface as Type).IsInterface)
        throw new ArgumentException("Only interfaces can be 'implemented'.");

    return (@interface as Type).IsAssignableFrom(type);
}

Приклад використання:

var testObject = new Dictionary<int, object>();
result = testObject.GetType().Implements(typeof(IDictionary<int, object>)); // true!

2
"IsAssignableFrom" було саме те, що я шукав - дякую
Jesper

22
Це не працює для вимоги запитувача не знати загального параметра типу. З вашого прикладу testObject.GetType (). Інструменти (typeof (IDictionary <,>)); повернеться помилковим.
ctusch

@ctusch тоді, будь-яке рішення для цього?
Тохід

5

Я використовую трохи простішу версію методу розширення @GenericProgrammers:

public static bool Implements<TInterface>(this Type type) where TInterface : class {
    var interfaceType = typeof(TInterface);

    if (!interfaceType.IsInterface)
        throw new InvalidOperationException("Only interfaces can be implemented.");

    return (interfaceType.IsAssignableFrom(type));
}

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

    if (!featureType.Implements<IFeature>())
        throw new InvalidCastException();

5
Він все ще не працює відповідно до вимоги оригінального питання, що стосується загальних інтерфейсів.
nathanchere

4

Ви повинні перевірити наявність побудованого типу загального інтерфейсу.

Вам доведеться зробити щось подібне:

foo is IBar<String>

тому що IBar<String>являє собою побудований тип. Причина, що ви повинні це зробити, полягає в тому, що якщо він Tне визначений у вашому чеку, компілятор не знає, чи маєте ви це на увазі IBar<Int32>або IBar<SomethingElse>.


4

Для того, щоб повністю вирішити систему типу, я думаю , вам потрібно працювати з рекурсією, наприклад IList<T>: ICollection<T>: IEnumerable<T>, без яких ви не знали б , що в IList<int>кінцевому рахунку знаряддя IEnumerable<>.

    /// <summary>Determines whether a type, like IList&lt;int&gt;, implements an open generic interface, like
    /// IEnumerable&lt;&gt;. Note that this only checks against *interfaces*.</summary>
    /// <param name="candidateType">The type to check.</param>
    /// <param name="openGenericInterfaceType">The open generic type which it may impelement</param>
    /// <returns>Whether the candidate type implements the open interface.</returns>
    public static bool ImplementsOpenGenericInterface(this Type candidateType, Type openGenericInterfaceType)
    {
        Contract.Requires(candidateType != null);
        Contract.Requires(openGenericInterfaceType != null);

        return
            candidateType.Equals(openGenericInterfaceType) ||
            (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition().Equals(openGenericInterfaceType)) ||
            candidateType.GetInterfaces().Any(i => i.IsGenericType && i.ImplementsOpenGenericInterface(openGenericInterfaceType));

    }

3

Перш за все public class Foo : IFoo<T> {}, не компілюється, тому що вам потрібно вказати клас замість T, але припускаючи, що ви робите щось на кшталтpublic class Foo : IFoo<SomeClass> {}

то якщо ви зробите

Foo f = new Foo();
IBar<SomeClass> b = f as IBar<SomeClass>;

if(b != null)  //derives from IBar<>
    Blabla();

2

У випадку, якщо вам потрібен метод розширення, який би підтримував загальні базові типи, а також інтерфейси, я розширив відповідь sduplooy:

    public static bool InheritsFrom(this Type t1, Type t2)
    {
        if (null == t1 || null == t2)
            return false;

        if (null != t1.BaseType &&
            t1.BaseType.IsGenericType &&
            t1.BaseType.GetGenericTypeDefinition() == t2)
        {
            return true;
        }

        if (InheritsFrom(t1.BaseType, t2))
            return true;

        return
            (t2.IsAssignableFrom(t1) && t1 != t2)
            ||
            t1.GetInterfaces().Any(x =>
              x.IsGenericType &&
              x.GetGenericTypeDefinition() == t2);
    }

1

Метод перевірки, чи тип успадковує або реалізує загальний тип:

   public static bool IsTheGenericType(this Type candidateType, Type genericType)
    {
        return
            candidateType != null && genericType != null &&
            (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition() == genericType ||
             candidateType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericType) ||
             candidateType.BaseType != null && candidateType.BaseType.IsTheGenericType(genericType));
    }

0

Спробуйте наступне розширення.

public static bool Implements(this Type @this, Type @interface)
{
    if (@this == null || @interface == null) return false;
    return @interface.GenericTypeArguments.Length>0
        ? @interface.IsAssignableFrom(@this)
        : @this.GetInterfaces().Any(c => c.Name == @interface.Name);
}

Щоб перевірити це. творити

public interface IFoo { }
public interface IFoo<T> : IFoo { }
public interface IFoo<T, M> : IFoo<T> { }
public class Foo : IFoo { }
public class Foo<T> : IFoo { }
public class Foo<T, M> : IFoo<T> { }
public class FooInt : IFoo<int> { }
public class FooStringInt : IFoo<string, int> { }
public class Foo2 : Foo { }

і метод випробування

public void Test()
{
    Console.WriteLine(typeof(Foo).Implements(typeof(IFoo)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<int>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<string>)));
    Console.WriteLine(typeof(FooInt).Implements(typeof(IFoo<,>)));
    Console.WriteLine(typeof(FooStringInt).Implements(typeof(IFoo<,>)));
    Console.WriteLine(typeof(FooStringInt).Implements(typeof(IFoo<string,int>)));
    Console.WriteLine(typeof(Foo<int,string>).Implements(typeof(IFoo<string>)));
 }

-1

Не повинно бути нічого поганого в наступному:

bool implementsGeneric = (anObject.Implements("IBar`1") != null);

Для отримання додаткового кредиту ви можете зловити AmbiguousMatchException, якщо ви хочете надати конкретний загальний параметр типу зі своїм запитом IBar.


Що ж, зазвичай краще уникати використання рядкових літералів, коли це можливо. Такий підхід ускладнить переробку програми, оскільки перейменування інтерфейсу IBar не змінить буквений рядок, і помилка буде виявлена ​​лише під час виконання.
andyroschy

Наскільки я зазвичай погоджуюся з коментарем вище щодо використання "магічних рядків" тощо, це все-таки найкращий підхід, який я знайшов. Досить близько - тестування на PropertyType.Name, що дорівнює "IWever`1".
натанчере

Чому б не це? bool implementsGeneric = (anObject.Implements(typeof(IBar<>).Name) != null);
Maxime Gélinas
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.