.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會遞歸調用這個方法來判斷繼承關系。

我已經根據自己的理解加上了注釋:

View Code
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的根源。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM