今天在研究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的根源。