Soru 'Unbox.any' neden 'castclass' yönteminin yardımcı bir istisna metni sunmuyor?


Sorumu açıklamak için şu önemsiz örnekleri (C #) düşünün:

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.
}

Dolayısıyla, bir referans dönüşümünü denediğimiz durumlarda (CIL talimatlarına uygun olarak) castclass), atılan istisna formun mükemmel bir mesajını içerir:

'Y' yazmak için 'X' türünde nesne dönüştürülemiyor.

Ampirik kanıtlar bu metin mesajının problemle başa çıkması gereken (deneyimli veya tecrübesiz) geliştirici (hata düzeltici) için genellikle çok yararlı olduğunu göstermektedir.

Aksine, bir kutu açma girişiminde bulunduğumuzda aldığımız mesaj (unbox.any) başarısız, oldukça bilgilendirici değil. Bunun böyle olması için teknik bir neden var mı?

Belirtilen atama geçerli değil. [YARARLI DEĞİL]

Başka bir deyişle, neden (benim kelimeler) gibi bir mesaj alamıyoruz:

'X' türünde bir nesneyi 'Y' türünde bir değere çıkartmak mümkün değil;   iki tür katılıyorum.

sırasıyla (sözlerim):

Boş olmayan bir gönderilemeyen 'Y' tipi bir değere çıkartılamıyor.

Bu yüzden soruyu tekrarlamak için: Bir vakadaki hata mesajının iyi ve bilgilendirici olduğunu ve diğer durumda da fakir olduğunu "yanlışlıkla" mı? Ya da çalışma zamanının ikinci vakada karşılaşılan gerçek tiplerin ayrıntılarını vermesinin imkansız veya zor bir şekilde yapılmasının teknik bir sebebi var mıdır?

(Burada SO üzerinde birkaç iş parçacığı görmüştüm, çünkü başarısız kutulama için istisna metninin daha iyi olup olmadığı sorulmayacaktı.)


Güncelleme: Daniel Frederico Lins Leite'in cevabı CLR Github'da bir konu açmasına yol açtı (aşağıya bakınız). Bu daha önceki bir konunun (Jon Skeet tarafından yükseltilmiş, insanlar neredeyse tahmin etmişti) bir kopyası olarak keşfedilmiştir. Bu nedenle, kötü istisna mesajının iyi bir nedeni yoktu ve insanlar bunu CLR'de zaten düzeltmişti. Bu yüzden bunu merak eden ilk ben değildim. Bu geliştirmenin .NET Framework'de gönderildiği günü sabırsızlıkla bekleyebiliriz.


22
2017-10-07 10:15


Menşei


Jon bu soruyu zaten sordu. Kabaca. Aynı nedenden dolayı, bu, .NET 1.x günlerinde kompakt ve hızlı kodu geri üretmek zorunda kaldı. İyi bir istisna mesajı istiyorsan, kodu yazmalısın. Convert.ToInt64(reference). Hala çok kompakt, hızlı değil. - Hans Passant
@HansPassant Yani, bağladığınız soru neden desenle ilgili? var nullable = box as int?; if (nullable.HasValue) { /* use nullable.Value */ } çok daha yavaş if (box is int) { var value = (int)box; /* use value */ }. Bu yüzden CIL talimatını biliyorum unbox.any Son örnekte kullanılan, hızlı olacaktır çünkü kopyalamaya dahil değildir. Ve her zaman, genel olmayan koleksiyonlarda basit değerlerin kutlandığı .NET 1 günlerinde hızlı olmalıydı. Ama soruma nasıl cevap veriyor? Tip kontrolünün başarısız olduğu "dalda", istisna daha fazla ayrıntı koyamaz mıyız? - Jeppe Stig Nielsen
Ayrıca castclass CIL talimatının inanılmaz derecede hızlı olması ve bu kadar hızlı olması gerekiyordu. ArrayList ve Hashtable Koleksiyonların güçlü yazılmadığı günler. castclass kopyalama yapmaz, sadece tip kontrolü ve referans aynı yere gider. Peki fark nerede? castclass tip kontrolünün başarısız olduğu durumlarda, istisna mesajında ​​türden ve türden yazılan "zengin" bir istisna yol açar. - Jeppe Stig Nielsen


Cevaplar:


TL; DR;

Çalışmanın mesajı geliştirmek için gereken tüm bilgilere sahip olduğunu düşünüyorum. Belki bir JIT geliştiricisi yardımcı olabilir, çünkü JIT kodunun çok hassas olduğunu ve performans veya güvenlik nedeniyle bazı kararların alındığını söylemek gereksizdir, bu da dışarıda bir kişinin anlaması için çok zordur.

Detaylı açıklama

Sorunu basitleştirmek için yöntemi değiştirdim:

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

Buradaki önemli op kodları:

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

Ve genel bellek düzeni:

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

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

Bu durumda çalışma zamanı StringBuilder için bir işaretçiye sahip olduğunu bilir ve onu bir dizeye atmalı. Bu durumda tüm bilgiler var Size mümkün olan en iyi istisnayı vermek için gerekli.

Eğer JIT'de görürsek https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/interpreter.cpp#L6137böyle bir şey göreceğiz

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

bu yönteme girersek

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

ve önemli kısım şöyle olurdu:

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);
    } 

Buradaki önemli kısım, istisnayı atan yöntemdir. Gördüğün gibi hem geçerli nesneyi hem de yayınlamaya çalıştığınız türü alır.

Sonunda, Atma yöntemi şu yöntemi çağırır:

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

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

Wich size tür isimleri ile güzel istisna mesaj verir.

Ancak bir nesneyi bir değer türüne gönderirken

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
}

Buradaki önemli işlem kodu:
http://msdn.microsoft.com/library/system.reflection.emit.opcodes.unbox_any.aspx

ve UnboxAny davranışını burada görebiliriz 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;
        }
}

Şey ... en azından, daha iyi bir istisna mesajı vermek mümkün görünüyor. İstisnanın ne zaman hoş bir mesaj aldığını hatırlarsanız, çağrı şuydu:

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

ve daha az bilgi verici mesaj oldu:

COMPlusThrow(kInvalidCastException);

Bu yüzden mesajın iyileştirilmesinin mümkün olduğunu düşünüyorum

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

Microsoft geliştiricileri görüşlerini görmek için coreclr github üzerinde aşağıdaki sorunu oluşturdum.

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


4
2017-10-16 17:47



Analiziniz için teşekkür ederim. Github ile ilgili bir sorun yaratırsanız harika olur, o zaman bu Stack Overflow iş parçacığına bağlantı kurabilirsiniz ve buradan Github'a bağlantı kurabilirdim. - Jeppe Stig Nielsen
Sorunu oluşturdum ve bağlantıyı buraya ekledim. Önerin için teşekkürler. - Daniel Frederico Lins Leite
Github sorununuz için ilginç bir yorum var. Soru metnine bir güncelleme ekledim. - Jeppe Stig Nielsen