.Net拾穗(二)刚发现的一个.Net的Bug
今天在研究Type.IsSubclassOf方法的时候用Reflector看内部的实现,无意中貌似发现了一个.Net的Bug。
首先不明白RuntimeType, RuntimeTypeHandler的同学可以看这里:
http://www.cnblogs.com/dudu/articles/9984.html
然后下面这段代码,大家认为执行结果是什么:
public class TestClassOne<T, F> where F : Exception
{
public class TestClassTow<F2> where F2 : F
{
public class TestClassThree<F3> where F3 : F2
{
}
}
}
static void Main(string[] args)
{
foreach (Type t in typeof(TestClassOne<,>.TestClassTow<>.TestClassThree<>).GetGenericArguments())
{
Console.WriteLine(t.ToString() + t.IsSubclassOf(typeof(Exception)));
}
Console.ReadLine();
}
如果你认为F,F2,F3都被判断为Exception的子类,那你就错了,实际执行结果是下面这样:
这个貌似是个Bug哦。
关键代码在这里,RunTimeType的GetBaseType方法,IsSubClassOf会递归调用这个方法来判断继承关系。
我已经根据自己的理解加上了注释:

private RuntimeType GetBaseType()
{
if (base.IsInterface)
{
return null;
}
if (!RuntimeTypeHandle.IsGenericVariable(this))
{//如果不是泛型类的参数类型,则直接调用内部函数返回结果
return RuntimeTypeHandle.GetBaseType(this);
}
//取得泛型参数类型约束
Type[] genericParameterConstraints = this.GetGenericParameterConstraints();
//结果为object.....
RuntimeType objectType = ObjectType;
for (int i = 0; i < genericParameterConstraints.Length; i++)
{//遍历约束
RuntimeType type2 = (RuntimeType) genericParameterConstraints[i];
if (!type2.IsInterface)
{
if (type2.IsGenericParameter)
{//如果Type2是另一个泛型参数
//得到泛型参数属性 & SpecialConstraintMask特殊约束掩码
GenericParameterAttributes attributes = type2.GenericParameterAttributes &
GenericParameterAttributes.SpecialConstraintMask;
/*
None没有任何特殊标志。
VarianceMask选择所有方差标志的组合。此值是使用逻辑“或”将标志 Contravariant 和 Covariant 进行组合的结果。
Covariant该泛型类型参数是协变的。协变类型参数可以作为方法的结果类型、只读字段的类型、声明的基类型或实现接口出现。
Contravariant该泛型类型参数是逆变的。逆变类型参数可以作为参数类型出现在方法签名中。
SpecialConstraintMask选择所有特殊约束标志的组合。此值是使用逻辑“或”将标志
DefaultConstructorConstraint、ReferenceTypeConstraint 和 NotNullableValueTypeConstraint 进行组合的结果。
ReferenceTypeConstraint仅当类型为引用类型时,才能替代泛型类型参数。
NotNullableValueTypeConstraint仅当类型是值类型且不可为空时,才能替代泛型类型参数。
DefaultConstructorConstraint仅当类型具有无参数构造函数时,才能替代泛型类型参数。*/
//不是引用类型 且 是可空类型
if (((attributes & GenericParameterAttributes.ReferenceTypeConstraint) ==
GenericParameterAttributes.None) &&
((attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) ==
GenericParameterAttributes.None))
{
goto Label_0055;
}
}
objectType = type2;
Label_0055:;
}
}
if (objectType == ObjectType)
{//对值类型和引用类型的判断
GenericParameterAttributes attributes2 = this.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
if ((attributes2 & GenericParameterAttributes.NotNullableValueTypeConstraint) != GenericParameterAttributes.None)
{//不可为空类型时为值类型
objectType = ValueType;
}
}
return objectType;
}
这段程序简而言之就是这样:
1如果一个类型是不是泛型类型那么调用内部函数取得父类。
2否则,取得泛型约束类型 GetGenericParameterConstraints(); 这里如果是F3它取得的结果就是F2,如果是F它取得的结果是Exception。
然后根据GenericParameterAttributes判断这个取得的约束类型,如果是引用类型,或者为非空类型,则该类型就被做父类。
3最后是一个对值类型的判断。
那么F3取得的结果F2,而F2的GenericParameterAttributes是什么呢?是None。
所以它在后面的所有判断中,与的结果都是Null,这使得它不能成为F3的父类,也就断绝了继承关系,最终使得F3在Exception的子类的判断中失败。
我们可以看到.Net在对泛型Foo<T>这样的处理中,是把T本身当做一个Type,而且作为参数传入的。但是在语言中T只是一个抽象符号,并不是一个具体的Type。因此当它成为另一个T2的父类的时候,就中断了继承链。我认为这正是这个Bug的根源。