jjzjj

c# - 为什么 'unbox.any' 不像 'castclass' 那样提供有用的异常文本?

coder 2023-07-12 原文

为了说明我的问题,请考虑以下简单示例 (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),抛出的异常包含以下形式的极好的消息:

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

经验证据表明,此文本消息通常对需要处理问题的(有经验或无经验的)开发人员(错误修复者)非常有帮助。

相比之下,当尝试拆箱 (unbox.any) 失败时,我们收到的消息相当不提供信息。有什么技术原因必须如此吗?

Specified cast is not valid. [NOT HELPFUL]

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

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

分别(又是我的话):

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

所以重复我的问题:错误消息在一种情况下很好且信息丰富,而在另一种情况下很差,这是否“偶然”?或者是否有技术原因导致运行时无法或极其困难地提供第二种情况下遇到的实际类型的详细信息?

(我在 SO 上看到了几个话题,我敢肯定,如果失败的拆箱的异常文本更好,我肯定不会被问到。)


更新:Daniel Frederico Lins Leite 的回答导致他在 CLR Github 上提出了一个问题(见下文)。这被发现是早期问题的重复(由 Jon Skeet 提出,人们几乎猜到了!)。因此,糟糕的异常消息没有充分的理由,人们已经在 CLR 中修复了它。所以我不是第一个对此感到疑惑的人。我们可以期待这一改进在 .NET Framework 中发布的那一天。

最佳答案

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 的指针 它应该将其转换为字符串。在这种情况下,它拥有所有信息 需要尽可能给你最好的异常(exception)。

如果我们在 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);
    } 

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

最后,Throw 方法调用了这个方法:

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

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

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

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

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

我们可以在这里看到 UnboxAny 行为 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());

信息量较少的消息是:

COMPlusThrow(kInvalidCastException);

所以我认为可以改进消息做

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

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

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

关于c# - 为什么 'unbox.any' 不像 'castclass' 那样提供有用的异常文本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39914845/

有关c# - 为什么 'unbox.any' 不像 'castclass' 那样提供有用的异常文本?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  3. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  4. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  5. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  6. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  7. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  8. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  9. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  10. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

随机推荐