Как определить, является ли тип другим общим типом - PullRequest
24 голосов
/ 16 сентября 2008

пример:

public static void DoSomething<K,V>(IDictionary<K,V> items) {
   items.Keys.Each(key => {
      if (items[key] **is IEnumerable<?>**) { /* do something */ }
      else { /* do something else */ }
}

Можно ли это сделать без использования отражения? Как мне сказать IEnumerable в C #? Должен ли я просто использовать IEnumerable, поскольку IEnumerable <> реализует IEnumerable?

Ответы [ 9 ]

102 голосов
/ 02 июля 2009

Большое спасибо за этот пост. Я хотел предоставить версию решения Конрада Рудольфа, которая работала бы лучше для меня. У меня были незначительные проблемы с этой версией, особенно при тестировании, если тип является типом значения, допускающим значение NULL:

public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
    var interfaceTypes = givenType.GetInterfaces();

    foreach (var it in interfaceTypes)
    {
        if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
            return true;
    }

    if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
        return true;

    Type baseType = givenType.BaseType;
    if (baseType == null) return false;

    return IsAssignableToGenericType(baseType, genericType);
}
40 голосов
/ 16 сентября 2008

Ранее принятый ответ это хорошо, но это неправильно. К счастью, ошибка маленькая. Проверка на IEnumerable не достаточна, если вы действительно хотите узнать об общей версии интерфейса; Есть много классов, которые реализуют только неуниверсальный интерфейс. Я дам ответ через минуту. Во-первых, я хотел бы отметить, что принятый ответ слишком сложен, поскольку следующий код достиг бы того же в данных обстоятельствах:

if (items[key] is IEnumerable)

Это делает еще больше, потому что работает для каждого элемента в отдельности (а не для их общего подкласса, V).

Теперь за правильное решение. Это немного сложнее, потому что мы должны взять универсальный тип IEnumerable`1 (то есть тип IEnumerable<> с одним параметром типа) и ввести правильный универсальный аргумент:

static bool IsGenericEnumerable(Type t) {
    var genArgs = t.GetGenericArguments();
    if (genArgs.Length == 1 &&
            typeof(IEnumerable<>).MakeGenericType(genArgs).IsAssignableFrom(t))
        return true;
    else
        return t.BaseType != null && IsGenericEnumerable(t.BaseType);
}

Вы можете легко проверить правильность этого кода:

var xs = new List<string>();
var ys = new System.Collections.ArrayList();
Console.WriteLine(IsGenericEnumerable(xs.GetType()));
Console.WriteLine(IsGenericEnumerable(ys.GetType()));

дает:

True
False

Не слишком переживайте из-за того, что здесь используется отражение. Хотя это правда, что это увеличивает накладные расходы во время выполнения, также как и использование оператора is.

Конечно, приведенный выше код ужасно ограничен и может быть расширен в более общеприменимый метод IsAssignableToGenericType. Следующая реализация немного некорректна 1 , и я оставлю ее здесь только для исторических целей . Не используйте его . Вместо этого Джеймс предоставил превосходную, правильную реализацию в своем ответе.

public static bool IsAssignableToGenericType(Type givenType, Type genericType) {
    var interfaceTypes = givenType.GetInterfaces();

    foreach (var it in interfaceTypes)
        if (it.IsGenericType)
            if (it.GetGenericTypeDefinition() == genericType) return true;

    Type baseType = givenType.BaseType;
    if (baseType == null) return false;

    return baseType.IsGenericType &&
        baseType.GetGenericTypeDefinition() == genericType ||
        IsAssignableToGenericType(baseType, genericType);
}

1 Сбой, когда genericType совпадает с givenType; по той же самой причине, это терпит неудачу для обнуляемых типов, то есть

IsAssignableToGenericType(typeof(List<int>), typeof(List<>)) == false
IsAssignableToGenericType(typeof(int?), typeof(Nullable<>)) == false

Я создал сущность с полным набором тестовых случаев .

5 голосов
/ 16 сентября 2008

Слово предупреждения об универсальных типах и использовании IsAssignableFrom () ...

Скажем, у вас есть следующее:

public class MyListBase<T> : IEnumerable<T> where T : ItemBase
{
}

public class MyItem : ItemBase
{
}

public class MyDerivedList : MyListBase<MyItem>
{
}

Вызов IsAssignableFrom для базового типа списка или для производного типа списка вернет false, но ясно, что MyDerivedList наследует MyListBase<T>. (Небольшое примечание для Джеффа: генерики абсолютно должны быть обернуты в блок кода или тильды, чтобы получить <T>, в противном случае он опущен. Это предусмотрено?) Проблема связана с тем, что MyListBase<MyItem> рассматривается как совершенно другой тип, чем MyListBase<T>. Следующая статья может объяснить это немного лучше. http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

Вместо этого попробуйте следующую рекурсивную функцию:

    public static bool IsDerivedFromGenericType(Type givenType, Type genericType)
    {
        Type baseType = givenType.BaseType;
        if (baseType == null) return false;
        if (baseType.IsGenericType)
        {
            if (baseType.GetGenericTypeDefinition() == genericType) return true;
        }
        return IsDerivedFromGenericType(baseType, genericType);
    }

/ РЕДАКТИРОВАТЬ: Новый пост Конрада, который учитывает общую рекурсию, а также интерфейсы, находится на месте. Очень хорошая работа. :)

/ EDIT2: Если выполняется проверка того, является ли genericType интерфейсом, преимущества в производительности могут быть реализованы. Проверка может быть блоком if для текущего кода интерфейса, но если вы заинтересованы в использовании .NET 3.5, мой друг предлагает следующее:

    public static bool IsAssignableToGenericType(Type givenType, Type genericType)
    {
        var interfaces = givenType.GetInterfaces().Where(it => it.IsGenericType).Select(it => it.GetGenericTypeDefinition());
        var foundInterface = interfaces.FirstOrDefault(it => it == genericType);
        if (foundInterface != null) return true;

        Type baseType = givenType.BaseType;
        if (baseType == null) return false;

        return baseType.IsGenericType ?
            baseType.GetGenericTypeDefinition() == genericType :
            IsAssignableToGenericType(baseType, genericType);
    }
4 голосов
/ 31 декабря 2011

Спасибо за отличную информацию. Для удобства я преобразовал это в метод расширения и сократил его до одного оператора.

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

Теперь его можно легко вызвать с помощью:

sometype.IsAssignableToGenericType (TypeOf (MyGenericType <>))

4 голосов
/ 16 сентября 2008
if (typeof(IEnumerable).IsAssignableFrom(typeof(V))) {
1 голос
/ 16 сентября 2008

Я бы использовал перегрузку:

public static void DoSomething<K,V>(IDictionary<K,V> items)
  where V : IEnumerable
{
   items.Keys.Each(key => { /* do something */ });
}

public static void DoSomething<K,V>(IDictionary<K,V> items)
{
   items.Keys.Each(key => { /* do something else */ });
}
0 голосов
/ 24 декабря 2008

Раньше я думал, что такая ситуация может быть решена способом, аналогичным решению @ 100 Томаса Данекера , но с добавлением другого аргумента шаблона:

public static void DoSomething<K, V, U>(IDictionary<K,V> items)
    where V : IEnumerable<U> { /* do something */ }
public static void DoSomething<K, V>(IDictionary<K,V> items)
                             { /* do something else */ }

Но теперь я заметил, что это не сработает, если я не укажу аргументы шаблона первого метода явно. Это явно не настраивается для каждого элемента в словаре, но это может быть своего рода решением для бедняков.

Я был бы очень благодарен, если бы кто-то мог указать на что-то неправильное, что я мог бы сделать здесь.

0 голосов
/ 16 сентября 2008

Вы хотите проверить метод Type.IsInstanceOfType

0 голосов
/ 16 сентября 2008

Я не уверен, что понимаю, что вы имеете в виду здесь. Вы хотите знать, имеет ли объект какой-либо универсальный тип или вы хотите проверить, является ли он конкретным универсальным типом ? Или вы просто хотите знать, перечислимо ли?

Я не думаю, что первое возможно. Второе, безусловно, возможно, просто относитесь к нему как к любому другому типу. В-третьих, просто протестируйте его на IEnumerable, как вы и предлагали.

Кроме того, вы не можете использовать оператор «is» для типов.

// Not allowed
if (string is Object)
  Foo();
// You have to use 
if (typeof(object).IsAssignableFrom(typeof(string))
  Foo();

См. этот вопрос о типах для более подробной информации. Может быть, это поможет вам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...