IsGenericType 和 IsGenericTypeDefinition 之间的区别

Difference between IsGenericType and IsGenericTypeDefinition

Type.IsGenericTypeType.IsGenericTypeDefinition 和有什么不一样?有趣的是,MSDN 的 IsGenericTypeDefinition link 被破坏了

尝试检索在给定 DbContext 中定义的所有 DbSet 后,我​​得到了以下结果,我试图理解这种行为:通过 IsGenericType 过滤属性 returns 期望的结果,而 IsGenericTypeDefinition 不是(return 没有)。

有趣的是,从 this post 我的印象是作者确实使用 IsGenericTypeDefinition 获得了他的 DbSet,而我没有。

下面是说明讨论的示例:

private static void Main(string[] args)
{
    A a = new A();
    int propertyCount = a.GetType().GetProperties().Where(p => p.PropertyType.IsGenericType).Count();
    int propertyCount2 = a.GetType().GetProperties().Where(p => p.PropertyType.IsGenericTypeDefinition).Count();

    Console.WriteLine("count1: {0}  count2: {1}", propertyCount, propertyCount2);
}

// Output: count1: 1  count2: 0

public class A
{
    public string aaa { get; set; }
    public List<int> myList { get; set; }
}

IsGenericType 告诉您 System.Type 的这个实例代表了一个指定了所有类型参数的泛型类型。例如,List<int> 是泛型。

另一方面,

IsGenericTypeDefinition 告诉您 System.Type 的这个实例表示一个定义,可以通过为其类型参数提供类型实参从中构造泛型类型。例如,List<> 是泛型类型定义。

您可以通过调用 GetGenericTypeDefinition:

来获取泛型类型的泛型定义
var listInt = typeof(List<int>);
var typeDef = listInt.GetGenericTypeDefinition(); // gives typeof(List<>)

您可以通过向 MakeGenericType:

提供类型参数来从泛型类型定义创建泛型类型
var listDef = typeof(List<>);
var listStr = listDef.MakeGenericType(typeof(string));

(此答案在下方并排 table 中比较了 Type 的所有泛型类型相关属性,因此如果您已经熟悉 .NET 的泛型并且只需要参考然后向下滚动到 table)


首先,记住参数参数的区别,尤其是w.r.t。 通用类型参数通用类型参数(以及通用方法类型参数泛型方法类型参数):

  • 通用类型参数是“开放”通用类型中声明的类型“占位符”。
    • 例如,在class Generic<T0,T1> {}中,T0T1符号是通用类型参数。请注意,当简单地给出一个未使用的通用 class 定义时,则没有 类型参数 .
  • 一个泛型参数是泛型[=256=的消费者为泛型参数指定的类型标识符].
    • 例如,在 Generic<String,Object> gen = new Generic<String,Object> 那么...
      • ...泛型类型参数 for 泛型类型参数 T0String.
      • ...泛型类型参数 for 泛型类型参数 T1Object.
    • 但是,通用类型参数不需要是具体类型:它们可以是来自使用者上下文的通用类型参数
      • 例如,在class Generic<TItem> { public Object Foo() { return new List<T>(); } }
        • ...然后Generic<TItem>中的泛型参数TItem被用作泛型参数T in List<T>Foo 方法内。
  • 是的,如果您对所有这些感到困惑,请不要担心,因为这很正常。
  • 最后,泛型方法类型参数泛型方法类型参数泛型类型参数 泛型类型参数 除了它们被限制在一个方法中:
    • 例如class NotGeneric { void AmGeneric<T>() { } }中的classNotGeneric没有任何泛型类型参数,但是它的方法AmGeneric() 有一个 泛型方法类型参数 。假设 AmGeneric() 永远不会 called/used/invoked(“通用实例化”)该方法没有任何 通用方法类型参数 .

鉴于这些 C# classes...

class NormalClass { }

class Generic<T> { }

class Derived : Generic<String> { }

class HasGenericMethod { public void Foo<T>() {} }

...这些 Type 个实例来自 GetGenericArguments():

Type[] genericTypeArgs = typeof(Generic<>).GetGenericArguments();
Type genTypeArg = genericTypeArgs.Single();

Type[] genericMethodTypeArgs = typeof(HasGenericMethod).GetMethod( nameof(HasGenericMethod.Foo) ).GetGenericArguments();
Type genMethodArg = genericMethodTypeArgs.Single();

...那么它们的 typeof() 表达式将具有这些属性:

Example typeof(NormalClass) typeof(Generic<>) typeof(Generic<String>) typeof(Derived) genTypeArg genMethodArg typeof(Generic<String>[])
Type properties
Type.IsTypeDefinition Yes Yes No Yes No No No
Type.IsGenericType No Yes Yes No No No No5
Type.ContainsGenericParameters No Yes No No Yes4 Yes4 No
Type.GenericTypeArguments Empty Empty { typeof(String) } Empty Empty Empty Empty
Type.IsConstructedGenericType No No Yes No No No No
Type.IsGenericTypeDefinition No Yes No No No No No
Generic parameter properties:
Type.IsGenericParameter No No No No Yes Yes No
Type.IsGenericMethodParameter No No No No No Yes No
Type.IsGenericTypeParameter No No No No Yes No No
Methods:
Type.GetGenericArguments() Empty { typeof(T) } { typeof(String) } Empty Empty Empty { typeof(String) }
Type.GetGenericParameterConstraints() Exception1 Exception1 Exception1 Exception1 Empty Empty Exception1
Type.GetGenericTypeDefinition() Exception2 typeof(Generic<>) typeof(Generic<>) Exception2 Exception2 Exception2 Exception2

您可以自己生成此 table(albiet,转置)using this LinqPad script


提醒自己:如果您有一个来自 Object.GetType()Type 对象,该对象可能是封闭的泛型类型(即 Object.GetType().IsConstructedGenericType == true),或者是派生自该泛型的非泛型类型,并且您想找出什么,请执行以下操作:

private static readonly Type _knownGenericType = typeof(Generic<>);

public static Boolean TryGetTypeArgsOfKnownGenericType( Object obj, [NotNullWhen(true)] out Type? actualArgType )
{
    Type t = obj.GetType();
    while( t != null )
    {
        if t.IsConstructedGenericType && t.GetGenericTypeDefinition() == _knownGenericType )
        {
            Type[] tArgs = t.GetGenericArguments();
            actualArgType = tArgs.Single();
            return true;
        }

        t = t.BaseType;
    }

    actualArgType = null;
    return false;
}

因此下面的代码将打印“成功:T := System.String”两次:

if( TryGetTypeArgsOfKnownGenericType( new Derived(), out Type? tArg ) )
{
    Console.WriteLine("Success: T := " + tArg.FullName);
}

if( TryGetTypeArgsOfKnownGenericType( new Generic<String>(), out Type? tArg ) )
{
    Console.WriteLine("Success: T := " + tArg.FullName);
}

脚注:

  1. InvalidOperationException:“只能在 Type.IsGenericParameter 为真的类型上调用方法。”

  2. InvalidOperationException: "此操作仅对泛型类型有效。"

  3. typeof(T)typeof(Generic<>).GetGenericArguments().Single()

  4. 令人惊讶的是 typeof(T).ContainsGenericParameters == trueT 是一个 泛型参数 没有参数集(即 Tundefined),所以我本以为会抛出一个 InvalidOperationException

    • The documentation for ContainsGenericParameters 似乎有理由返回 true(强调我的):

      For convenience and to reduce the chance of error, the ContainsGenericParameters property provides a standard way to distinguish between closed constructed types, which can be instantiated, and open constructed types, which cannot. If the ContainsGenericParameters property returns true, the type cannot be instantiated.

  5. 显然在 T 是构造泛型类型时使用 typeof(T[])ContainsGenericParameters 属性 是 falseGetGenericArguments() 方法 returns 是 T 的类型参数的非空数组,而不是 System.Array 的类型参数(实际上不是通用类型)。

    • 例如:
      typeof(Generic<String>[]).IsGenericType         == false
      typeof(Generic<String>[]).GetGenericArguments() == new[] { typeof(String) }
      
    • 这在上面最右边的一栏中有记载table。
    • 并在文档中描述:

      The ContainsGenericParameters property searches recursively for type parameters. For example, it returns true for an array whose elements are type A<T> even though the array is not itself generic. Contrast this with the behavior of the IsGenericType property, which returns false for arrays.