пример:
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?





if (typeof(IEnumerable).IsAssignableFrom(typeof(V))) {
Я не совсем уверен. Я думаю, что среда выполнения имеет достаточно информации в системе типов, чтобы ответить на вопрос IsAssignableFrom без отражения сборки.
Я не уверен, что понимаю, что вы здесь имеете в виду. Вы хотите знать, является ли объект любой общий тип, или вы хотите проверить, является ли он конкретный универсальный тип? Или вы просто хотите знать, можно ли перечислить?
Не думаю, что первое возможно. Второй вариант определенно возможен, просто относитесь к нему как к любому другому типу. Что касается третьего, просто протестируйте его на IEnumerable, как вы предложили.
Кроме того, вы не можете использовать оператор is для типов.
// Not allowed
if (string is Object)
Foo();
// You have to use
if (typeof(object).IsAssignableFrom(typeof(string))
Foo();
Подробнее см. этот вопрос о типах. Может, это тебе поможет.
Я хочу знать, реализует ли V в моем примере IEnumerable чего-либо, и мне все равно, что. Я должен был сказать, что items [key] в моем примере является IEnumerable <?>. Спасибо, что уловили это.
Вы хотите проверить метод Тип.IsInstanceOfType
Я бы использовал перегрузку:
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 */ });
}
Ранее принятый ответ - это хорошо, но это неправильно. К счастью, ошибка небольшая. Проверки 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; по той же причине он не работает для типов, допускающих значение NULL, т.е.
IsAssignableToGenericType(typeof(List<int>), typeof(List<>)) == false
IsAssignableToGenericType(typeof(int?), typeof(Nullable<>)) == false
Я создал gist с исчерпывающим набором тестовых примеров.
Это именно то, что я искал. Спасибо! Он по-прежнему использует отражение, но я искал синтаксис typeof (IEnumerable <>). Спасибо!
Нет ли другого способа проверить тип, кроме как с помощью «is»?
последняя строка должна читать return (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == genericType) || IsAssignableToGenericType(baseType, genericType);. Вы не можете избежать рекурсии только потому, что базовый класс является универсальным.
@Ben: Вы правы, конечно, спасибо. К счастью, по крайней мере некоторые люди действительно внимательно читают код. ;-) - я опустил скобки, так как && в любом случае связывается сильнее, чем ||. Но, пожалуйста, еще раз проверьте, есть ли у вас время, я еще не достигла пикового уровня кофеина.
@Joel Вы правы, спасибо, что указали на это: мой ответ содержит ошибки, как указано в вашем предыдущем комментарии, и ответ Джеймса, похоже, работает. К сожалению, его ответ похоронен глубоко внизу, и только OP может изменить принятый ответ. Но я обновлю свой ответ, чтобы отразить это.
Предупреждение об общих типах и использовании 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);
}
/ EDIT: новый пост Конрада, который учитывает общую рекурсию, а также интерфейсы, находится на высоте. Очень хорошая работа. :)
/ 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);
}
Замечание Джеффу: это сделано специально, потому что Markdown допускает встроенный HTML. Спасибо за уведомление, я интегрирую его в свой код.
Рич, я на самом деле предпочитаю ваш код моему, но, к сожалению, он не работает, если genericType является типом интерфейса, о котором идет речь. Однако это легко исправить.
Раньше я думал, что такая ситуация может быть решена способом, аналогичным решение @Thomas Danecker, но с добавлением еще одного аргумента шаблона:
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 */ }
Но теперь я заметил, что это не сработает, если я не укажу явно аргументы шаблона для первого метода. Это явно не настраивается для каждого элемента в словаре, но это может быть своего рода решением для бедняков.
Я был бы очень благодарен, если бы кто-нибудь мог указать на что-нибудь неправильное, что я мог здесь сделать.
Большое спасибо за этот пост Я хотел предоставить версию решения Конрада Рудольфа, которая мне больше подошла. У меня были незначительные проблемы с этой версией, особенно при проверке, является ли Type типом значения, допускающим значение 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);
}
Отличная работа! Вот сокращенная версия для тех, кто предпочитает краткость: public static bool IsAssignableToGenericType(this Type type, Type genericType) { return type.GetInterfaces().Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType) || (type.IsGenericType && type.GetGenericTypeDefinition() == genericType) || (type.BaseType != null && IsAssignableToGenericType(type.BaseType, genericType)); }
Это здорово, но похоже, что это было бы намного быстрее, если бы вы переместили прямое сравнение так, чтобы оно было выше сравнения интерфейсов, если прямое сравнение соответствует?
@Moeri Сделайте его еще короче, исключив избыточный предикат и используя ?.: public static bool IsAssignableToGenericType( this Type type, Type generic ) { return new[] { type }.Concat( type.GetTypeInfo().ImplementedInterfaces ).Any( i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == generic ) || (type.GetTypeInfo().BaseType?.IsAssignableToGenericType( generic ) ?? false); }
Я считаю, что проверка interfaceTypes, когда метод использует рекурсию (то есть последняя строчка кода, которая проверяет baseType), расточительна? GetInterfaces возвращает все реализованные или унаследованные интерфейсы, так что проверка не требуется для BaseType, верно?
Спасибо за отличную информацию. Для удобства я преобразовал это в метод расширения и сократил его до одного оператора.
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 <>))
Это использует отражение. Это работает ... но мне было интересно, можете ли вы сделать это без рефлексии, особенно как вы говорите, что укажите неспециализированный универсальный тип в C#. Спасибо!