为什么"unbox.any"不像"castclass"那样提供有用的异常文本? [英] Why does 'unbox.any' not provide a helpful exception text the way 'castclass' does?

查看:98
本文介绍了为什么"unbox.any"不像"castclass"那样提供有用的异常文本?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为说明我的问题,请考虑以下简单示例(C#):

To illustrate my question, consider these trivial examples (C#):

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),抛出的异常包含以下形式的出色消息:

So in the cases where we attempt a reference conversion (corresponding to CIL instruction castclass), the exception thrown contains an excellent message of the form:

无法将类型为"X"的对象转换为类型为"Y".

Unable to cast object of type 'X' to type 'Y'.

经验证据表明,此文本消息通常对于需要解决该问题的(经验丰富或经验不足的)开发人员(错误修复程序)非常有用.

Empirical evidence shows that this text message is often extremely helpful for the (experienced or inexperienced) developer (bug fixer) who needs to deal with the problem.

相反,当尝试取消装箱(unbox.any)失败时,我们收到的消息是没有信息的.有任何技术原因为什么一定要这么做?

In contrast, the message we get when an attempted unboxing (unbox.any) fails, is rather non-informative. Is there any technical reason why this must be so?

指定的转换无效. [不乐于助人]

Specified cast is not valid. [NOT HELPFUL]

换句话说,为什么我们没有收到像(我的话)这样的消息:

In other words, why do we not receive a message like (my words):

无法将"X"类型的对象拆箱为"Y"类型的值; 这两种类型必须一致.

Unable to unbox an object of type 'X' into a value of type 'Y'; the two types must agree.

分别(再次我的话):

无法将空引用拆箱到非空类型'Y'的值中.

Unable to unbox a null reference into a value of the non-nullable type 'Y'.

因此重复我的问题:错误消息在一种情况下是好的且可提供信息,而在另一种情况下是很差的信息是偶然"的吗?还是有技术上的原因,使得运行时无法提供或很难提供第二种情况下遇到的实际类型的详细信息?

So to repeat my question: Is it "accidental" that the error message in one case is good and informative, and in the other case is poor? Or is there a technical reason why it would be impossible, or prohibitively difficult, for the runtime to provide details of the actual types encountered in the second case?

(我在SO上看到了几个线程,我相信永远也不会问到失败装箱失败的异常文本是否更好.)

(I have seen a couple of threads here on SO that I am sure would never have been asked if the exception text for failed unboxings had been better.)

更新:Daniel Frederico Lins Leite的回答导致他在CLR Github上发布了一个问题(请参阅下文).人们发现这是先前一期的复本(由乔恩·斯凯特(Jon Skeet)提出,人们几乎猜到了!).因此,没有充分的理由来说明不良的异常消息,并且人们已经在CLR中对其进行了修复.因此,我不是第一个对此感到疑惑的人.我们可以期待在.NET Framework中实现这一改进的那一天.

Update: Daniel Frederico Lins Leite's answer led to him opening an issue on the CLR Github (see below). This was discovered to be a duplicate of an earlier issue (raised by Jon Skeet, people almost guessed it!). So there was no good reason for the poor exception message, and people had already fixed it in the CLR. So I was not the first to wonder about this. We can look forward to the day when this improvement ships in the .NET Framework.

推荐答案

TL; DR;

我认为运行时具有改善消息所需的所有信息.也许有些JIT开发人员可以提供帮助,因为不用说JIT代码非常敏感,有时由于性能或安全性原因而做出决策,这是局外人很难理解的.

I think that the runtime have all information needed to improve the message. Maybe some JIT developer could help, because it is needless to say that the JIT code is very sensitive and some times decisions are taken because of performance or security reasons, that are very difficult to an outsider to understand.

详细说明

为简化问题,我将方法更改为:

To simplify the problem I changed the method to:

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的指针 它应该将其转换为字符串.在这种情况下,它具有所有信息 需要给您最好的例外.

So in this case the runtime knows that it has a pointer to a StringBuilder and it should cast it to a string. In this situation it has all the information needed to give you the best exception as possible.

如果我们在JIT上看到 https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/interpreter.cpp#L6137 我们会看到类似的东西

If we see at the JIT https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/interpreter.cpp#L6137 we will se something like that

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

如果我们深入研究此方法

if we dig into this method

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

这里重要的部分是引发异常的方法.如你看到的 它会同时接收当前对象和您尝试转换为的类型.

The important part here is the method that throws the exception. As you can see it receives both the current object and the type that you trying to cast to.

最后,Throw方法调用此方法:

At the end, the Throw method calls this method:

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

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

Wich为您提供了漂亮的带有类型名称的异常消息.

Wich gives you the nice exception message with the type names.

但是在将对象转换为值类型时

But when you are casting a object to a value type

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

the important opcode here is:
http://msdn.microsoft.com/library/system.reflection.emit.opcodes.unbox_any.aspx

,我们可以在此处看到UnboxAny行为 https://github.com/dotnet/coreclr/blob/32f0f9721afb584b4a14d69135bea7ddc129f755/src/vm/interpreter.cpp#L8766

and we can see the UnboxAny behavior here 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;
        }
}

好吧……至少,似乎有可能给出更好的异常消息. 如果您还记得该异常有一个不错的消息,那么呼叫是:

Well... at least, it seems possible to give a better exception message. If you remember when the exception had a nice message the call was:

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

信息较少的是:

COMPlusThrow(kInvalidCastException);

所以我认为有可能改善消息处理能力

So I think that it is possible to improve the message doing

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

我在coreclr github上创建了以下问题,以查看Microsoft开发人员的意见.

I have created the following issue on the coreclr github to see what is Microsoft developers opinions.

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

这篇关于为什么"unbox.any"不像"castclass"那样提供有用的异常文本?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆