检查对象是否属于 C# 中的非特定泛型类型

本文关键字:非特 泛型类型 对象 是否 属于 检查 | 更新日期: 2023-09-27 18:30:33

假设我有以下类:

public class General<T> { }

我想找出一个对象是否属于这种类型。我知道我可以使用反射来确定对象是否属于带有Type.GetGenericTypeDefinition的泛型类型,但我想避免这种情况。

有没有可能做一些像obj is General<T>obj.GetType().IsAssignableFrom(typeof(General<T>))的事情?

我很惊讶我找不到类似的问题,尽管我可能在搜索中使用了错误的关键字。

检查对象是否属于 C# 中的非特定泛型类型

你可以这样做:

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()));
}

致谢斯莱克回答

类似的问题有很多答案,但它们都需要反思才能沿着类型层次结构向上走。我怀疑没有更好的方法。如果性能至关重要,缓存结果可能是一种选择。下面是一个使用 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

使用类型参数实例化的泛型类型定义与其他泛型类型实例化完全没有关系。它们也与泛型类型定义无关。在赋值和运行时强制转换方面,它们是完全不兼容的。如果不是这样,就有可能破坏类型系统。

因此,运行时强制转换将无济于事。你确实将不得不求助于Type.GetGenericTypeDefinition.您可以将其抽象为帮助程序函数,并以这种方式保持代码相对干净。

如果泛型类或接口具有成员,这些成员可由代码使用,这些代码以更通用的形式(如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指示它是可写的并且知道如何比较项目),而无需了解元素类型。 如果泛型接口继承自非泛型接口,则需要非泛型函数的代码可以简单地强制转换为非泛型接口类型并直接使用它。

对于适用于任何父类型(基类和接口)的更通用的解决方案:

    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>))

IsCompatibleWith(typeof(List<>), typeof(IList<>))

IsCompatibleWith(typeof(List<int>), typeof(IList<>))

IsCompatibleWith(typeof(List<int>), typeof(IList<string>))