2016-10-07 46 views
22

爲了說明我的問題,考慮這些簡單的例子(C#):爲什麼'unbox.any'不能像'castclass'那樣提供有用的異常文本?

object reference = new StringBuilder(); 
object box = 42; 
object unset = null; 

// CASE ONE: bad reference conversions (CIL instrcution 0x74 'castclass') 
try 
{ 
    string s = (string)reference; 
} 
catch (InvalidCastException ice) 
{ 
    Console.WriteLine(ice.Message); // Unable to cast object of type 'System.Text.StringBuilder' to type 'System.String'. 
} 
try 
{ 
    string s = (string)box; 
} 
catch (InvalidCastException ice) 
{ 
    Console.WriteLine(ice.Message); // Unable to cast object of type 'System.Int32' to type 'System.String'. 
} 

// CASE TWO: bad unboxing conversions (CIL instrcution 0xA5 'unbox.any') 
try 
{ 
    long l = (long)reference; 
} 
catch (InvalidCastException ice) 
{ 
    Console.WriteLine(ice.Message); // Specified cast is not valid. 
} 
try 
{ 
    long l = (long)box; 
} 
catch (InvalidCastException ice) 
{ 
    Console.WriteLine(ice.Message); // Specified cast is not valid. 
} 
try 
{ 
    long l = (long)unset; 
} 
catch (NullReferenceException nre) 
{ 
    Console.WriteLine(nre.Message); // Object reference not set to an instance of an object. 
} 
在我們試圖引用轉換(對應於CIL指令 castclass)的情況下

所以,拋出的異常包含表單的一個極好的消息, :

無法投射'X'類型的對象以鍵入'Y'。

經驗證據顯示,這條短信通常對需要處理問題的(有經驗或缺乏經驗的)開發人員(缺陷修復人員)非常有幫助。

相比之下,當嘗試拆箱(unbox.any)失敗時,我們收到的消息是無法提供信息的。有什麼技術上的原因,爲什麼這一定是如此?

指定的轉換無效。 [無用]

換句話說,我們爲什麼不接收等(我的話)的消息:

無法拆箱類型的「X」的對象成Y型的」的值「; 這兩種類型必須同意。

分別(再次我的話):

無法拆箱空引用到非空類型「Y」的值。

因此,重複我的問題:在一個案例中的錯誤信息是好的和信息豐富的,而在另一個案例中是差的是「偶然的」?或者是否有一個技術原因,爲什麼運行時不可能提供或者非常難以提供第二種情況下遇到的實際類型的細節?

(我見過一對夫婦的線程在這裏讓我相信絕不會有人問,如果失敗unboxings的異常文本一直比較好。)


更新:丹尼爾弗雷德里科·林斯萊特的回答導致他在CLR Github上開啓一個問題(見下文)。這被發現是早期問題的複製品(由Jon Skeet提出,人們幾乎猜到了!)。所以沒有很好的理由來解釋這個糟糕的異常消息,而且人們已經將它固定在CLR中。所以我不是第一個想知道這個的。我們可以期待這一改進在.NET Framework中出現的那一天。

+1

喬恩[已經問過這個問題(http://stackoverflow.com/questions/1583050/performance-surprise-with-as-and-nullable-types)。大致。同樣的原因,這必須在.NET 1.x時間內生成緊湊且快速的代碼。如果你想要一個很好的異常消息,那麼你必須編寫代碼'Convert.ToInt64(reference)'。仍然非常緊湊,不如一樣快。 –

+1

@HansPassant所以你鏈接的問題是關於爲什麼模式'var nullable = box作爲int?如果(nullable.HasValue){/ *使用nullable.Value * /}'比'if(box is int){var value =(int)box;/*使用值* /}'。所以我知道在後面的例子中使用的CIL指令'unbox.any'將會很快,因爲不涉及複製。而且,在那些.NET中,只要簡單的值始終放在非泛型集合中,就必須快速。 __但它是如何回答我的問題的?__在類型檢查失敗的「分支」中,我們不能將更多細節放入異常嗎? –

+0

此外,'castclass' CIL指令預計會非常快速,並且在那些集合不是強類型的'ArrayList'和'Hashtable'天必須如此快。 'castclass'不復制,只是類型檢查和參考去同一個地方。那麼區別在哪裏?在類型檢查失敗的情況下,'castclass'會導致在異常消息中拼寫出的類型和類型的「豐富」異常。 –

回答

4

TL; DR;

我認爲運行時具有改善消息所需的所有信息。也許有些JIT開發人員可以提供幫助,因爲不用說JIT代碼非常敏感,並且有時候會因爲性能或安全原因而作出決定,這對於外人來說是非常難以理解的。

詳細解釋

爲了簡化我改變該方法的問題:

C#

void StringBuilderCast() 
{ 
    object sbuilder = new StringBuilder(); 
    string s = (string)sbuilder; 
} 

IL

.method private hidebysig 
    instance void StringBuilderCast() cil managed 
{ 
    // Method begins at RVA 0x214c 
    // Code size 15 (0xf) 
    .maxstack 1 
    .locals init (
     [0] object sbuilder, 
     [1] string s 
    ) 

    IL_0000: nop 
    IL_0001: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() 
    IL_0006: stloc.0 
    IL_0007: ldloc.0 
    IL_0008: castclass [mscorlib]System.String 
    IL_000d: stloc.1 
    IL_000e: ret 
} // end of method Program::StringBuilderCast 

重要這裏操作碼爲:

http://msdn.microsoft.com/library/system.reflection.emit.opcodes.newobj.aspx http://msdn.microsoft.com/library/system.reflection.emit.opcodes.castclass.aspx

而一般的內存佈局是:

Thread Stack      Heap 
+---------------+   +---+---+----------+ 
| some variable | +---->| L | T | DATA | 
+---------------+ |  +---+---+----------+ 
| sbuilder2 |----+ 
+---------------+ 

T = Instance Type 
L = Instance Lock 
Data = Instance Data 

因此,在這種情況下,運行時知道它有一個指向StringBuilder的 ,它應該將它轉換爲字符串。在這種情況下,它有所有的信息 需要儘可能給你最好的例外。

如果我們在JIT看到 https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/interpreter.cpp#L6137 我們將自身類似的東西

CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Casting InterpTracingArg(RTK_CastClass)); 
Object * pObj = OpStackGet<Object*>(idx); 
ObjIsInstanceOf(pObj, TypeHandle(cls), TRUE)) //ObjIsInstanceOf will throw if cast can't be done 

如果我們深入這個方法

https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/eedbginterfaceimpl.cpp#L1633

和重要組成部分是:

BOOL fCast = FALSE; 
TypeHandle fromTypeHnd = obj->GetTypeHandle(); 
if (fromTypeHnd.CanCastTo(toTypeHnd)) 
    { 
     fCast = TRUE; 
    } 
if (Nullable::IsNullableForType(toTypeHnd, obj->GetMethodTable())) 
    { 
     // allow an object of type T to be cast to Nullable<T> (they have the same representation) 
     fCast = TRUE; 
    } 
    // If type implements ICastable interface we give it a chance to tell us if it can be casted 
    // to a given type. 
    else if (toTypeHnd.IsInterface() && fromTypeHnd.GetMethodTable()->IsICastable()) 
    { 
    ... 
    } 
if (!fCast && throwCastException) 
    { 
     COMPlusThrowInvalidCastException(&obj, toTypeHnd); 
    } 

這裏的重要部分是拋出異常的方法。正如你所看到的 它接收當前對象和你試圖轉換的類型。

最後,投擲方法調用此方法:

https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/excep.cpp#L13997

COMPlusThrow(kInvalidCastException, IDS_EE_CANNOTCAST, strCastFromName.GetUnicode(), strCastToName.GetUnicode()); 

至極爲您提供了與類型名漂亮的異常信息。

但是當你施放對象爲值類型

C#

void StringBuilderToLong() 
{ 
    object sbuilder = new StringBuilder(); 
    long s = (long)sbuilder; 
} 

IL

.method private hidebysig 
    instance void StringBuilderToLong() cil managed 
{ 
    // Method begins at RVA 0x2168 
    // Code size 15 (0xf) 
    .maxstack 1 
    .locals init (
     [0] object sbuilder, 
     [1] int64 s 
    ) 

    IL_0000: nop 
    IL_0001: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor() 
    IL_0006: stloc.0 
    IL_0007: ldloc.0 
    IL_0008: unbox.any [mscorlib]System.Int64 
    IL_000d: stloc.1 
    IL_000e: ret 
} 

重要的操作碼在這裏:
http://msdn.microsoft.com/library/system.reflection.emit.opcodes.unbox_any.aspx

我們可以看到Unb oxAny行爲在這裏 https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/interpreter.cpp#L8766

//GET THE BOXED VALUE FROM THE STACK 
Object* obj = OpStackGet<Object*>(tos); 

//GET THE TARGET TYPE METADATA 
unsigned boxTypeTok = getU4LittleEndian(m_ILCodePtr + 1); 
boxTypeClsHnd = boxTypeResolvedTok.hClass; 
boxTypeAttribs = m_interpCeeInfo.getClassAttribs(boxTypeClsHnd); 

//IF THE TARGET TYPE IS A REFERENCE TYPE 
//NOTHING CHANGE FROM ABOVE 
if ((boxTypeAttribs & CORINFO_FLG_VALUECLASS) == 0) 
{ 
    !ObjIsInstanceOf(obj, TypeHandle(boxTypeClsHnd), TRUE) 
} 
//ELSE THE TARGET TYPE IS A REFERENCE TYPE 
else 
{ 
    unboxHelper = m_interpCeeInfo.getUnBoxHelper(boxTypeClsHnd); 
    switch (unboxHelper) 
     { 
     case CORINFO_HELP_UNBOX: 
       MethodTable* pMT1 = (MethodTable*)boxTypeClsHnd; 
       MethodTable* pMT2 = obj->GetMethodTable(); 

       if (pMT1->IsEquivalentTo(pMT2)) 
       { 
        res = OpStackGet<Object*>(tos)->UnBox(); 
       } 
       else 
       { 
        CorElementType type1 = pMT1->GetInternalCorElementType(); 
        CorElementType type2 = pMT2->GetInternalCorElementType(); 

        // we allow enums and their primtive type to be interchangable 
        if (type1 == type2) 
        { 
          res = OpStackGet<Object*>(tos)->UnBox(); 
        } 
       } 

     //THE RUNTIME DOES NOT KNOW HOW TO UNBOX THIS ITEM 
       if (res == NULL) 
       { 
        COMPlusThrow(kInvalidCastException); 

        //I INSERTED THIS COMMENTS 
      //auto thCastFrom = obj->GetTypeHandle(); 
      //auto thCastTo = TypeHandle(boxTypeClsHnd); 
      //RealCOMPlusThrowInvalidCastException(thCastFrom, thCastTo); 
       } 
       break; 
     case CORINFO_HELP_UNBOX_NULLABLE: 
       InterpreterType it = InterpreterType(&m_interpCeeInfo, boxTypeClsHnd); 
       size_t sz = it.Size(&m_interpCeeInfo); 
       if (sz > sizeof(INT64)) 
       { 
        void* destPtr = LargeStructOperandStackPush(sz); 
        if (!Nullable::UnBox(destPtr, ObjectToOBJECTREF(obj), (MethodTable*)boxTypeClsHnd)) 
        { 
         COMPlusThrow(kInvalidCastException); 
        //I INSERTED THIS COMMENTS 
      //auto thCastFrom = obj->GetTypeHandle(); 
      //auto thCastTo = TypeHandle(boxTypeClsHnd); 
      //RealCOMPlusThrowInvalidCastException(thCastFrom, thCastTo); 
        } 
       } 
       else 
       { 
        INT64 dest = 0; 
        if (!Nullable::UnBox(&dest, ObjectToOBJECTREF(obj), (MethodTable*)boxTypeClsHnd)) 
        { 
         COMPlusThrow(kInvalidCastException); 
        //I INSERTED THIS COMMENTS 
      //auto thCastFrom = obj->GetTypeHandle(); 
      //auto thCastTo = TypeHandle(boxTypeClsHnd); 
      //RealCOMPlusThrowInvalidCastException(thCastFrom, thCastTo); 
        } 
       } 
      } 
      break; 
     } 
} 

嗯...至少,似乎有可能提供更好的例外信息。 如果你還記得當異常有一個不錯的消息電話是:

COMPlusThrow(kInvalidCastException, IDS_EE_CANNOTCAST, strCastFromName.GetUnicode(), strCastToName.GetUnicode()); 

和少inforative消息是:

COMPlusThrow(kInvalidCastException); 

所以我認爲這是可以提高的消息做

auto thCastFrom = obj->GetTypeHandle(); 
auto thCastTo = TypeHandle(boxTypeClsHnd); 
RealCOMPlusThrowInvalidCastException(thCastFrom, thCastTo); 

我在coreclr github上創建了以下問題,以瞭解Microsoft開發人員的看法。

https://github.com/dotnet/coreclr/issues/7655

+0

感謝您的分析。如果你在Github上創建了一個問題,那就太好了,然後你可以鏈接到這個Stack Overflow線程,我可以從這裏鏈接到Github。 –

+1

我已經創建了該問題並在此處插入了鏈接。感謝您的建議。 –

+1

對您的Github問題有一個有趣的評論。我附加了一個更新到我的問題文本。 –

相關問題