假设我有以下类:
public class General<T> { }
我想确定一个对象是否为特定类型。
我知道可以使用反射来确定对象是否为该泛型类型,使用 Type.GetGenericTypeDefinition
,但我想避免这样做。
类似 obj is General<T>
或者 obj.GetType().IsAssignableFrom(typeof(General<T>))
这样的方法可行吗?
我很惊讶没有找到类似的问题,虽然可能是我在搜索时使用了错误的关键字。
var obj = new General<int>();
var type = obj.GetType();
var isGeneral =
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(General<>)) ||
type.GetBaseTypes().Any(x => x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(General<>));
下面是扩展方法GetBaseTypes
:
public static IEnumerable<Type> GetBaseTypes(this Type type)
{
if (type.BaseType == null) return type.GetInterfaces();
return new []{type}.Concat(
Enumerable.Repeat(type.BaseType, 1)
.Concat(type.GetInterfaces())
.Concat(type.GetInterfaces().SelectMany<Type, Type>(GetBaseTypes))
.Concat(type.BaseType.GetBaseTypes()));
}
GetGenericTypeDefinition()
对于非泛型类型会抛出异常。从我阅读的问题来看,您应该能够为 int
调用它(并获得 false
),并且您应该能够为 class I : General<int>
调用它(并获得 true
)。 - user743382IsAssignableFrom
对于开放式泛型类型没有意义,因为它们永远不可能是可变类型,因此永远不会相互分配。 - usrclass I
的异常,但是给出了错误的结果。 new I() is General<int>
会返回true
,但是你的检查不会。 - user743382Type.GetGenericTypeDefinition
吗? - EZLearnertype.GetBaseTypes()
不包括 type
本身,因此对于您答案中的测试 (new General<int>
) 返回 false。 - user743382type.GetInterfaces().SelectMany<Type, Type>(GetBaseTypes)
的需求是什么?type.GetInterfaces
不会返回所有接口,甚至包括基类吗? - nawfal有很多关于相似 问题的答案,但它们都需要反思来遍历类型层次结构。我怀疑没有更好的方法。如果性能很关键,缓存结果可能是一个选项。这里有一个例子,使用ConcurrentDictionary
作为简单的缓存。然后成本降低到一个简单的类型查找(通过GetType
)和在缓存被初始化后进行ConcurrentDictionary
查找。
using System.Collections.Concurrent;
private static ConcurrentDictionary<Tuple<Type,Type>, bool> cache = new ConcurrentDictionary<Tuple<Type,Type>, bool>();
public static bool IsSubclassOfRawGeneric(this Type toCheck, Type generic) {
var input = Tuple.Create(toCheck, generic);
bool isSubclass = cache.GetOrAdd(input, key => IsSubclassOfRawGenericInternal(toCheck, generic));
return isSubclass;
}
private static bool IsSubclassOfRawGenericInternal(Type toCheck, Type generic) {
while (toCheck != null && toCheck != typeof(object)) {
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
if (generic == cur) {
return true;
}
toCheck = toCheck.BaseType;
}
return false;
}
class I : General<int> { }
object o = new I();
Console.WriteLine(o is General<int>); // true
Console.WriteLine(o.GetType().IsSubclassOfRawGeneric(typeof(General<>))); //true
如果一个通用的类或接口有成员可以通过代码使用,这些代码在更普遍的形式下持有引用,比如Object
,但没有实际的泛型类型可用,则应该在非泛型基类或接口中公开这些成员。在许多情况下,框架未能遵守这一原则,但没有理由必须遵循他们的例子。例如,像IList<T>
这样的类型可能已从IListBase
派生,后者包括或继承了这样的成员:
int Count {get;}
void Delete(int index);
void Clear();
void Swap(int index1, int index2);
int Compare(int index1, int index2);
// Return an object with a `StoreToIndex(int)` method
// which would store it to the list it came from.
ListItemHolder GetItemHolder(int index);
ListFeatures Features {get;}
这些成员都不会以任何方式依赖于列表中所持有的项目类型,一个人可以编写方法来执行诸如对列表进行排序(如果其Features
指示它是可写的并且知道如何比较项目)之类的操作,而无需了解元素类型的任何信息。如果泛型接口继承自非泛型接口,需要使用非泛型函数的代码可以简单地将其强制转换为非泛型接口类型并直接使用。
使用类型参数实例化的通用类型定义与其他通用类型实例化根本没有关系。它们也与通用类型定义没有关系。当涉及到赋值和运行时转换时,它们是完全不兼容的。如果它们是兼容的,那么就有可能破坏类型系统。
因此,运行时转换是无法帮助的。您确实需要使用 Type.GetGenericTypeDefinition
。您可以将其抽象为一个辅助函数,并以这种方式保持代码相对清晰。
X<T>
,它似乎有点不同,即如果(1)类型X<>
在涉及类型参数T
时是协变或逆变的,并且(2)相同的类型参数T
被限制为特定的基类B
。在这种情况下,对于协变的X<>
,typeof(X<B>).IsAssignableFrom(typeof(X<>))
或者对于逆变性,typeof(X<>).IsAssignableFrom(typeof(X<B>))
。我不确定这是否是“意外”的。当然,X
必须是接口或委托类型,以便成为协变或逆变。 - Jeppe Stig Nielsen为了得到一个更加通用的解决方案,可以适用于任何父类型(包括基类和接口):
public static bool IsCompatibleWith(this Type type, Type parentType)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (parentType.IsAssignableFrom(type))
{
return true;
}
return type.GetAssignableTypes()
.Where(t => t.IsGenericType)
.Any(t=> t.GetGenericTypeDefinition() == parentType);
}
/// <summary>
/// Gets all parent types including the currrent type.
/// </summary>
public static IEnumerable<Type> GetAssignableTypes(this Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
// First check for interfaces because interface types don't have base classes.
foreach (Type iType in type.GetInterfaces())
{
yield return iType;
}
// Then check for base classes.
do
{
yield return type;
type = type.BaseType;
}
while (type != null);
}
IsCompatibleWith
是具有误导性的。也许可以使用IsKindOf
?此外,GetAssignableTypes
也可以称为GetParentTypes
,但这也是具有误导性的。命名很难。更好的做法是对其进行文档化。
一些测试:
IsCompatibleWith(typeof(List<int>), typeof(IList<int>))
true
IsCompatibleWith(typeof(List<>), typeof(IList<>))
true
IsCompatibleWith(typeof(List<int>), typeof(IList<>))
true
IsCompatibleWith(typeof(List<int>), typeof(IList<string>))
false
obj
的运行时类型为X
,并且X
有基类General<int>
,那会怎样? - Jeppe Stig Nielsen