类型参数详解
简介
本文档详细介绍 ITypeParameterSymbol(类型参数符号),包括类型参数约束、泛型方法和类型、协变和逆变。
适合人群: 中级开发者
预计阅读时间: 35 分钟
前置知识: 语法树基础、符号系统、泛型概念
📋 本文导航
| 章节 | 难度 | 阅读时间 | 链接 |
|---|---|---|---|
| 核心属性 | 🟢 | 8 分钟 | 查看 |
| 类型参数约束 | 🟡 | 12 分钟 | 查看 |
| 泛型方法和类型 | 🟡 | 10 分钟 | 查看 |
| 协变和逆变 | 🟡 | 5 分钟 | 查看 |
🟢 核心属性
ITypeParameterSymbol 表示泛型类型参数,如 List<T> 中的 T。
基本信息属性
csharp
ITypeParameterSymbol typeParameter;
// 类型参数名称(如 T, TKey, TValue)
string name = typeParameter.Name;
// 类型参数的序号(从 0 开始)
int ordinal = typeParameter.Ordinal;
// 类型参数的种类(始终是 TypeParameter)
TypeKind typeKind = typeParameter.TypeKind; // TypeKind.TypeParameter
// 是否是引用类型(根据约束判断)
bool isReferenceType = typeParameter.IsReferenceType;
// 是否是值类型(根据约束判断)
bool isValueType = typeParameter.IsValueType;
// 声明类型参数的符号(类或方法)
ISymbol declaringSymbol = typeParameter.DeclaringMethod ?? (ISymbol)typeParameter.DeclaringType;约束属性
csharp
// 是否有 class 约束
bool hasReferenceTypeConstraint = typeParameter.HasReferenceTypeConstraint;
// 是否有 struct 约束
bool hasValueTypeConstraint = typeParameter.HasValueTypeConstraint;
// 是否有 new() 约束
bool hasConstructorConstraint = typeParameter.HasConstructorConstraint;
// 是否有 unmanaged 约束
bool hasUnmanagedTypeConstraint = typeParameter.HasUnmanagedTypeConstraint;
// 是否有 notnull 约束
bool hasNotNullConstraint = typeParameter.HasNotNullConstraint;
// 类型约束列表
ImmutableArray<ITypeSymbol> constraintTypes = typeParameter.ConstraintTypes;协变和逆变属性
csharp
// 协变/逆变类型(None, Out, In)
VarianceKind variance = typeParameter.Variance;
// 是否是协变(out)
bool isCovariant = typeParameter.Variance == VarianceKind.Out;
// 是否是逆变(in)
bool isContravariant = typeParameter.Variance == VarianceKind.In;示例:分析类型参数基本信息
csharp
public void AnalyzeTypeParameterBasicInfo(ITypeParameterSymbol typeParameter)
{
Console.WriteLine($"类型参数名称: {typeParameter.Name}");
Console.WriteLine($"序号: {typeParameter.Ordinal}");
// 显示声明位置
if (typeParameter.DeclaringMethod != null)
{
Console.WriteLine($"声明在方法: {typeParameter.DeclaringMethod.ToDisplayString()}");
}
else if (typeParameter.DeclaringType != null)
{
Console.WriteLine($"声明在类型: {typeParameter.DeclaringType.ToDisplayString()}");
}
// 显示约束
Console.WriteLine($"\n约束:");
if (typeParameter.HasReferenceTypeConstraint)
Console.WriteLine(" - class (引用类型约束)");
if (typeParameter.HasValueTypeConstraint)
Console.WriteLine(" - struct (值类型约束)");
if (typeParameter.HasConstructorConstraint)
Console.WriteLine(" - new() (构造函数约束)");
if (typeParameter.HasUnmanagedTypeConstraint)
Console.WriteLine(" - unmanaged (非托管类型约束)");
if (typeParameter.HasNotNullConstraint)
Console.WriteLine(" - notnull (非空约束)");
// 显示类型约束
if (typeParameter.ConstraintTypes.Length > 0)
{
Console.WriteLine(" 类型约束:");
foreach (var constraint in typeParameter.ConstraintTypes)
{
Console.WriteLine($" - {constraint.ToDisplayString()}");
}
}
// 显示协变/逆变
if (typeParameter.Variance != VarianceKind.None)
{
Console.WriteLine($"\n协变/逆变: {typeParameter.Variance}");
}
}提示
类型参数的 Ordinal 属性表示它在类型参数列表中的位置,从 0 开始。
🟡 类型参数约束
类型参数可以有多种约束,限制可以用作类型实参的类型。
引用类型约束(class)
csharp
/// <summary>
/// 分析引用类型约束
/// </summary>
public void AnalyzeReferenceTypeConstraint(ITypeParameterSymbol typeParameter)
{
if (typeParameter.HasReferenceTypeConstraint)
{
Console.WriteLine($"类型参数 {typeParameter.Name} 有 class 约束");
Console.WriteLine("只能使用引用类型作为类型实参");
// 检查是否还有其他约束
if (typeParameter.ConstraintTypes.Length > 0)
{
Console.WriteLine("\n额外的类型约束:");
foreach (var constraint in typeParameter.ConstraintTypes)
{
Console.WriteLine($" - {constraint.ToDisplayString()}");
}
}
}
}值类型约束(struct)
csharp
/// <summary>
/// 分析值类型约束
/// </summary>
public void AnalyzeValueTypeConstraint(ITypeParameterSymbol typeParameter)
{
if (typeParameter.HasValueTypeConstraint)
{
Console.WriteLine($"类型参数 {typeParameter.Name} 有 struct 约束");
Console.WriteLine("只能使用值类型作为类型实参");
// struct 约束隐含 new() 约束
if (typeParameter.HasConstructorConstraint)
{
Console.WriteLine("注意: struct 约束隐含 new() 约束");
}
}
}构造函数约束(new())
csharp
/// <summary>
/// 分析构造函数约束
/// </summary>
public void AnalyzeConstructorConstraint(ITypeParameterSymbol typeParameter)
{
if (typeParameter.HasConstructorConstraint)
{
Console.WriteLine($"类型参数 {typeParameter.Name} 有 new() 约束");
Console.WriteLine("类型实参必须有公共无参构造函数");
// 检查是否与 struct 约束一起使用
if (typeParameter.HasValueTypeConstraint)
{
Console.WriteLine("注意: struct 约束已经隐含 new() 约束");
}
}
}类型约束
csharp
/// <summary>
/// 分析类型约束
/// </summary>
public void AnalyzeTypeConstraints(ITypeParameterSymbol typeParameter)
{
if (typeParameter.ConstraintTypes.Length == 0)
{
Console.WriteLine($"类型参数 {typeParameter.Name} 没有类型约束");
return;
}
Console.WriteLine($"类型参数 {typeParameter.Name} 的类型约束:");
foreach (var constraint in typeParameter.ConstraintTypes)
{
Console.WriteLine($"\n约束类型: {constraint.ToDisplayString()}");
Console.WriteLine($" 类型种类: {constraint.TypeKind}");
if (constraint.TypeKind == TypeKind.Interface)
{
Console.WriteLine(" 这是接口约束");
}
else if (constraint.TypeKind == TypeKind.Class)
{
Console.WriteLine(" 这是基类约束");
}
else if (constraint.TypeKind == TypeKind.TypeParameter)
{
Console.WriteLine(" 这是类型参数约束(裸类型约束)");
}
}
}非托管类型约束(unmanaged)
csharp
/// <summary>
/// 分析非托管类型约束
/// </summary>
public void AnalyzeUnmanagedConstraint(ITypeParameterSymbol typeParameter)
{
if (typeParameter.HasUnmanagedTypeConstraint)
{
Console.WriteLine($"类型参数 {typeParameter.Name} 有 unmanaged 约束");
Console.WriteLine("只能使用非托管类型作为类型实参");
Console.WriteLine("非托管类型包括:");
Console.WriteLine(" - 基本类型(int, float, bool 等)");
Console.WriteLine(" - 枚举类型");
Console.WriteLine(" - 指针类型");
Console.WriteLine(" - 只包含非托管类型字段的结构");
}
}notnull 约束
csharp
/// <summary>
/// 分析 notnull 约束
/// </summary>
public void AnalyzeNotNullConstraint(ITypeParameterSymbol typeParameter)
{
if (typeParameter.HasNotNullConstraint)
{
Console.WriteLine($"类型参数 {typeParameter.Name} 有 notnull 约束");
Console.WriteLine("类型实参不能是可空引用类型");
}
}点击查看完整示例:约束验证器
csharp
/// <summary>
/// 类型参数约束验证器
/// </summary>
public class TypeParameterConstraintValidator
{
/// <summary>
/// 验证类型是否满足类型参数的所有约束
/// </summary>
public bool ValidateConstraints(
ITypeParameterSymbol typeParameter,
ITypeSymbol typeArgument)
{
Console.WriteLine($"验证类型 {typeArgument.ToDisplayString()} 是否满足约束");
Console.WriteLine();
bool isValid = true;
// 1. 检查引用类型约束
if (typeParameter.HasReferenceTypeConstraint)
{
if (!typeArgument.IsReferenceType)
{
Console.WriteLine("❌ 不满足 class 约束:类型不是引用类型");
isValid = false;
}
else
{
Console.WriteLine("✓ 满足 class 约束");
}
}
// 2. 检查值类型约束
if (typeParameter.HasValueTypeConstraint)
{
if (!typeArgument.IsValueType)
{
Console.WriteLine("❌ 不满足 struct 约束:类型不是值类型");
isValid = false;
}
else
{
Console.WriteLine("✓ 满足 struct 约束");
}
}
// 3. 检查构造函数约束
if (typeParameter.HasConstructorConstraint)
{
bool hasPublicParameterlessConstructor = HasPublicParameterlessConstructor(typeArgument);
if (!hasPublicParameterlessConstructor)
{
Console.WriteLine("❌ 不满足 new() 约束:类型没有公共无参构造函数");
isValid = false;
}
else
{
Console.WriteLine("✓ 满足 new() 约束");
}
}
// 4. 检查类型约束
foreach (var constraint in typeParameter.ConstraintTypes)
{
bool satisfiesConstraint = SatisfiesTypeConstraint(typeArgument, constraint);
if (!satisfiesConstraint)
{
Console.WriteLine($"❌ 不满足类型约束: {constraint.ToDisplayString()}");
isValid = false;
}
else
{
Console.WriteLine($"✓ 满足类型约束: {constraint.ToDisplayString()}");
}
}
Console.WriteLine();
Console.WriteLine(isValid ? "验证通过 ✓" : "验证失败 ❌");
return isValid;
}
private bool HasPublicParameterlessConstructor(ITypeSymbol type)
{
if (type is not INamedTypeSymbol namedType)
return false;
// 值类型总是有默认构造函数
if (namedType.IsValueType)
return true;
// 检查是否有公共无参构造函数
var constructors = namedType.InstanceConstructors;
return constructors.Any(c =>
c.DeclaredAccessibility == Accessibility.Public &&
c.Parameters.Length == 0);
}
private bool SatisfiesTypeConstraint(ITypeSymbol type, ITypeSymbol constraint)
{
// 检查是否派生自约束类型或实现了约束接口
if (constraint.TypeKind == TypeKind.Interface)
{
if (type is INamedTypeSymbol namedType)
{
return namedType.AllInterfaces.Contains(
constraint as INamedTypeSymbol,
SymbolEqualityComparer.Default);
}
}
else if (constraint.TypeKind == TypeKind.Class)
{
var current = type;
while (current != null)
{
if (SymbolEqualityComparer.Default.Equals(current, constraint))
return true;
if (current is INamedTypeSymbol namedCurrent)
current = namedCurrent.BaseType;
else
break;
}
}
return false;
}
}关键要点:
- 引用类型约束和值类型约束是互斥的
- struct 约束隐含 new() 约束
- 类型约束可以是基类、接口或其他类型参数
- 可以同时有多个类型约束
- 约束的顺序有要求:class/struct → 类型约束 → new()
🟡 泛型方法和类型
类型参数可以声明在类型或方法上。
类型的类型参数
csharp
/// <summary>
/// 分析类型的类型参数
/// </summary>
public void AnalyzeTypeTypeParameters(INamedTypeSymbol typeSymbol)
{
if (!typeSymbol.IsGenericType)
{
Console.WriteLine("这不是泛型类型");
return;
}
Console.WriteLine($"泛型类型: {typeSymbol.ToDisplayString()}");
Console.WriteLine($"类型参数数量: {typeSymbol.TypeParameters.Length}");
Console.WriteLine();
for (int i = 0; i < typeSymbol.TypeParameters.Length; i++)
{
var typeParam = typeSymbol.TypeParameters[i];
Console.WriteLine($"类型参数 {i}: {typeParam.Name}");
// 显示约束
var constraints = GetConstraintDescription(typeParam);
if (!string.IsNullOrEmpty(constraints))
{
Console.WriteLine($" 约束: {constraints}");
}
Console.WriteLine();
}
}方法的类型参数
csharp
/// <summary>
/// 分析方法的类型参数
/// </summary>
public void AnalyzeMethodTypeParameters(IMethodSymbol methodSymbol)
{
if (!methodSymbol.IsGenericMethod)
{
Console.WriteLine("这不是泛型方法");
return;
}
Console.WriteLine($"泛型方法: {methodSymbol.ToDisplayString()}");
Console.WriteLine($"类型参数数量: {methodSymbol.TypeParameters.Length}");
Console.WriteLine();
for (int i = 0; i < methodSymbol.TypeParameters.Length; i++)
{
var typeParam = methodSymbol.TypeParameters[i];
Console.WriteLine($"类型参数 {i}: {typeParam.Name}");
// 显示约束
var constraints = GetConstraintDescription(typeParam);
if (!string.IsNullOrEmpty(constraints))
{
Console.WriteLine($" 约束: {constraints}");
}
Console.WriteLine();
}
}
private string GetConstraintDescription(ITypeParameterSymbol typeParam)
{
var constraints = new List<string>();
if (typeParam.HasReferenceTypeConstraint)
constraints.Add("class");
if (typeParam.HasValueTypeConstraint)
constraints.Add("struct");
if (typeParam.HasUnmanagedTypeConstraint)
constraints.Add("unmanaged");
if (typeParam.HasNotNullConstraint)
constraints.Add("notnull");
foreach (var constraint in typeParam.ConstraintTypes)
{
constraints.Add(constraint.ToDisplayString());
}
if (typeParam.HasConstructorConstraint)
constraints.Add("new()");
return string.Join(", ", constraints);
}区分类型参数和类型实参
csharp
/// <summary>
/// 区分类型参数和类型实参
/// </summary>
public void DistinguishTypeParametersAndArguments(INamedTypeSymbol typeSymbol)
{
Console.WriteLine($"类型: {typeSymbol.ToDisplayString()}");
Console.WriteLine();
// 类型参数(来自原始定义)
var originalDef = typeSymbol.OriginalDefinition;
Console.WriteLine("类型参数(Type Parameters):");
foreach (var typeParam in originalDef.TypeParameters)
{
Console.WriteLine($" - {typeParam.Name} (类型参数符号)");
}
Console.WriteLine();
// 类型实参(当前类型的实际类型)
if (!typeSymbol.IsUnboundGenericType)
{
Console.WriteLine("类型实参(Type Arguments):");
foreach (var typeArg in typeSymbol.TypeArguments)
{
Console.WriteLine($" - {typeArg.ToDisplayString()} ({typeArg.TypeKind})");
}
}
}裸类型约束
csharp
/// <summary>
/// 分析裸类型约束(类型参数约束另一个类型参数)
/// </summary>
public void AnalyzeNakedTypeConstraint(ITypeParameterSymbol typeParameter)
{
var nakedConstraints = typeParameter.ConstraintTypes
.Where(c => c.TypeKind == TypeKind.TypeParameter)
.Cast<ITypeParameterSymbol>()
.ToList();
if (nakedConstraints.Count == 0)
{
Console.WriteLine("没有裸类型约束");
return;
}
Console.WriteLine($"类型参数 {typeParameter.Name} 的裸类型约束:");
foreach (var constraint in nakedConstraints)
{
Console.WriteLine($" - {constraint.Name}");
Console.WriteLine($" 序号: {constraint.Ordinal}");
}
}
// 示例:class MyClass<T, U> where U : T
// U 有裸类型约束 T🟡 协变和逆变
协变和逆变允许泛型类型参数在继承层次中更灵活地使用。
协变(out)
csharp
/// <summary>
/// 分析协变类型参数
/// </summary>
public void AnalyzeCovariance(ITypeParameterSymbol typeParameter)
{
if (typeParameter.Variance != VarianceKind.Out)
{
Console.WriteLine("这不是协变类型参数");
return;
}
Console.WriteLine($"协变类型参数: {typeParameter.Name}");
Console.WriteLine("使用 out 修饰符");
Console.WriteLine();
Console.WriteLine("协变规则:");
Console.WriteLine(" - 只能用作返回类型");
Console.WriteLine(" - 不能用作方法参数类型");
Console.WriteLine(" - 允许向上转型");
Console.WriteLine();
Console.WriteLine("示例:");
Console.WriteLine(" IEnumerable<string> strings = ...;");
Console.WriteLine(" IEnumerable<object> objects = strings; // 合法");
}逆变(in)
csharp
/// <summary>
/// 分析逆变类型参数
/// </summary>
public void AnalyzeContravariance(ITypeParameterSymbol typeParameter)
{
if (typeParameter.Variance != VarianceKind.In)
{
Console.WriteLine("这不是逆变类型参数");
return;
}
Console.WriteLine($"逆变类型参数: {typeParameter.Name}");
Console.WriteLine("使用 in 修饰符");
Console.WriteLine();
Console.WriteLine("逆变规则:");
Console.WriteLine(" - 只能用作方法参数类型");
Console.WriteLine(" - 不能用作返回类型");
Console.WriteLine(" - 允许向下转型");
Console.WriteLine();
Console.WriteLine("示例:");
Console.WriteLine(" IComparer<object> objectComparer = ...;");
Console.WriteLine(" IComparer<string> stringComparer = objectComparer; // 合法");
}不变(invariant)
csharp
/// <summary>
/// 分析不变类型参数
/// </summary>
public void AnalyzeInvariance(ITypeParameterSymbol typeParameter)
{
if (typeParameter.Variance != VarianceKind.None)
{
Console.WriteLine("这不是不变类型参数");
return;
}
Console.WriteLine($"不变类型参数: {typeParameter.Name}");
Console.WriteLine("没有 in 或 out 修饰符");
Console.WriteLine();
Console.WriteLine("不变规则:");
Console.WriteLine(" - 可以用作返回类型");
Console.WriteLine(" - 可以用作方法参数类型");
Console.WriteLine(" - 不允许类型转换");
Console.WriteLine();
Console.WriteLine("示例:");
Console.WriteLine(" List<string> strings = ...;");
Console.WriteLine(" List<object> objects = strings; // 非法");
}点击查看完整示例:协变逆变分析器
csharp
/// <summary>
/// 协变逆变分析器
/// </summary>
public class VarianceAnalyzer
{
public void AnalyzeTypeVariance(INamedTypeSymbol typeSymbol)
{
if (!typeSymbol.IsGenericType)
{
Console.WriteLine("这不是泛型类型");
return;
}
Console.WriteLine($"分析类型: {typeSymbol.ToDisplayString()}");
Console.WriteLine($"类型种类: {typeSymbol.TypeKind}");
Console.WriteLine();
// 只有接口和委托可以有协变逆变
if (typeSymbol.TypeKind != TypeKind.Interface &&
typeSymbol.TypeKind != TypeKind.Delegate)
{
Console.WriteLine("注意: 只有接口和委托可以有协变逆变类型参数");
Console.WriteLine();
}
// 分析每个类型参数
Console.WriteLine("类型参数:");
foreach (var typeParam in typeSymbol.TypeParameters)
{
Console.WriteLine($"\n {typeParam.Name}:");
Console.WriteLine($" 序号: {typeParam.Ordinal}");
Console.WriteLine($" 协变/逆变: {GetVarianceDescription(typeParam.Variance)}");
// 显示约束
if (typeParam.ConstraintTypes.Length > 0 ||
typeParam.HasReferenceTypeConstraint ||
typeParam.HasValueTypeConstraint ||
typeParam.HasConstructorConstraint)
{
Console.WriteLine($" 约束: {GetConstraintDescription(typeParam)}");
}
// 检查协变逆变的使用
if (typeParam.Variance != VarianceKind.None)
{
AnalyzeVarianceUsage(typeSymbol, typeParam);
}
}
}
private string GetVarianceDescription(VarianceKind variance)
{
return variance switch
{
VarianceKind.Out => "协变 (out)",
VarianceKind.In => "逆变 (in)",
VarianceKind.None => "不变",
_ => "未知"
};
}
private void AnalyzeVarianceUsage(
INamedTypeSymbol typeSymbol,
ITypeParameterSymbol typeParam)
{
Console.WriteLine($" 使用位置:");
// 分析方法中的使用
var methods = typeSymbol.GetMembers().OfType<IMethodSymbol>();
foreach (var method in methods)
{
// 检查返回类型
if (UsesTypeParameter(method.ReturnType, typeParam))
{
Console.WriteLine($" - 返回类型: {method.Name}");
}
// 检查参数类型
foreach (var param in method.Parameters)
{
if (UsesTypeParameter(param.Type, typeParam))
{
Console.WriteLine($" - 参数类型: {method.Name}({param.Name})");
}
}
}
}
private bool UsesTypeParameter(ITypeSymbol type, ITypeParameterSymbol typeParam)
{
if (SymbolEqualityComparer.Default.Equals(type, typeParam))
return true;
if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
{
return namedType.TypeArguments.Any(arg => UsesTypeParameter(arg, typeParam));
}
return false;
}
private string GetConstraintDescription(ITypeParameterSymbol typeParam)
{
var constraints = new List<string>();
if (typeParam.HasReferenceTypeConstraint)
constraints.Add("class");
if (typeParam.HasValueTypeConstraint)
constraints.Add("struct");
foreach (var constraint in typeParam.ConstraintTypes)
{
constraints.Add(constraint.ToDisplayString());
}
if (typeParam.HasConstructorConstraint)
constraints.Add("new()");
return string.Join(", ", constraints);
}
}关键要点:
- 协变(out):类型参数只能用作输出(返回类型)
- 逆变(in):类型参数只能用作输入(参数类型)
- 只有接口和委托可以有协变逆变类型参数
- 协变允许向上转型,逆变允许向下转型
- 不变类型参数不允许任何转换
注意
协变和逆变只能用于接口和委托类型,不能用于类和结构。