【问题标题】:Check if object is of non-specific generic type in C#在 C# 中检查对象是否属于非特定泛型类型
【发布时间】:2013-09-12 07:04:14
【问题描述】:

假设我有以下课程:

public class General<T> { }

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

是否可以执行obj is General&lt;T&gt;obj.GetType().IsAssignableFrom(typeof(General&lt;T&gt;)) 之类的操作?

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

【问题讨论】:

  • 出于好奇,如果你有答案,你打算用它做什么?如果没有反思,您将无能为力,而您的问题要求避免反思。
  • 我试图避免无用的反射,尽管它的运行时性能是 baaaaaad(就像一只羊 :))
  • @EZSlaver 为了满足我的 的好奇心,您是否有一个场景来证明问题出在哪里?关于反思缓慢的论点已经过去了,但我觉得它已经被污名化到了在任何解决方案中都避免迷信的地步。
  • 你的班级不是sealed。如果obj 是运行时类型X,而X 有基类General&lt;int&gt; 怎么办?
  • @JeppeStigNielsen,请改写你的问题。

标签: c# .net generics types


【解决方案1】:

你可以这样做:

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

感谢Slacks answer

【讨论】:

  • GetGenericTypeDefinition() 为非泛型类型抛出异常。按照我阅读问题的方式,您应该可以将其称为int(并获得false),并且您应该可以将其称为class I : General&lt;int&gt;(并获得true)。跨度>
  • 很高兴知道。虽然我认为IsAssignableFrom 对于开放的泛型类型毫无意义,因为它们永远不可能是变量类型,因此永远不会相互分配。
  • 该检查避免了我的class I 的异常,但给出了错误的结果。 new I() is General&lt;int&gt; 会返回 true,但您的支票不会。
  • 你能避免要求Type.GetGenericTypeDefinition吗?
  • 几乎是对的:type.GetBaseTypes() 本身不包括 type,因此这会为您的答案中的测试返回 false (new General&lt;int&gt;)。
【解决方案2】:

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

【讨论】:

  • 请记住,这仅适用于类层次结构测试,不适用于接口测试
【解决方案3】:

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

因此,运行时强制转换无济于事。您确实必须求助于Type.GetGenericTypeDefinition。你可以把它抽象成一个辅助函数,这样你的代码就相对干净了。

【讨论】:

  • 当然,这在大多数情况下是正确的。我发现了一个示例X&lt;T&gt;,它似乎有点不同,即如果(1)类型X&lt;&gt;在所讨论的类型参数T中是协变或逆变的,并且(2)相同的类型参数@ 987654325@ 被限制为特定的基类B。在这种情况下,typeof(X&lt;B&gt;).IsAssignableFrom(typeof(X&lt;&gt;))(用于协变 X&lt;&gt;)或typeof(X&lt;&gt;).IsAssignableFrom(typeof(X&lt;B&gt;))(用于逆变)。我不确定这是否是“无意的”。当然X 必须是接口或委托类型才能协变或逆变。
【解决方案4】:

如果泛型类或接口的成员可以被代码使用,这些代码以更通用的形式(如 Object)持有引用,但没有可用的实际泛型类型,则此类成员应以非通用基类或接口。该框架在许多情况下未能遵守这一原则,但没有理由必须效仿他们。例如,IList&lt;T&gt; 之类的类型可能派生自 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 表明它是可写的并且知道如何比较items),而无需了解元素类型。如果泛型接口继承自非泛型接口,则需要非泛型函数的代码可以简单地转换为非泛型接口类型并直接使用它。

【讨论】:

    【解决方案5】:

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

        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&lt;int&gt;), typeof(IList&lt;int&gt;))
    真的

    IsCompatibleWith(typeof(List&lt;&gt;), typeof(IList&lt;&gt;))
    真的

    IsCompatibleWith(typeof(List&lt;int&gt;), typeof(IList&lt;&gt;))
    真的

    IsCompatibleWith(typeof(List&lt;int&gt;), typeof(IList&lt;string&gt;))
    假的

    【讨论】:

      猜你喜欢
      • 2010-11-02
      • 1970-01-01
      • 1970-01-01
      • 2020-06-15
      • 2021-02-06
      • 2016-01-14
      • 1970-01-01
      相关资源
      最近更新 更多