• 小心!Struct陷阱


    最近编码时遇到了一个问题,先看一个程序:

    static void Main ( string[ ] args )
    {
    MyValueType mv
    = new MyValueType ( b: 2.3 );
    object objMv = mv;
    Console.WriteLine ( mv );
    Console.WriteLine ( objMv );
    /* Step1 */
    ( (MyValueType) objMv ).Method1 (
    3.4 );
    Console.WriteLine ( objMv );
    /* Step2 */
    ( (IMyValueType) mv ).Method1 (
    4.5 );
    Console.WriteLine ( mv );
    /* Step3 */
    ( (IMyValueType) objMv ).Method1 (
    5.6 );
    Console.WriteLine ( objMv );

    Console.ReadKey (
    true);
    }
    struct MyValueType:IMyValueType{
    public int a;
    public double b;
    public bool c;
    public MyValueType ( int a=default(int),
    double b=default(double),bool c=default(bool)) {
    this.a=a;
    this.b = b;
    this.c = c;
    }
    public override string ToString ( )
    {
    return string.Format ( "a={0},b={1},c={2}", a, b,c );
    //return base.ToString ( );
    }
    public void Method1 ( double b = default(double) )
    {
    this.b = b;
    }
    }
    interface IMyValueType {
    void Method1 ( double b = default(double) );
    }

    问:上述3个步骤的控制台输出是什么?(主要看b的值)

    各位先猜猜b的值在3个步骤中输出是什么,然后复制粘贴运行下,看看是不是答对了!答对了的童鞋也请想想为什么会是这样的结果。

     

    下面引出主题

    Struct(值类型)的装箱和拆箱

    一、预备知识

    1.1 继承

    所有值类型都是sealed的,即不能被继承。struct不能继承其它类型(其实已经继承自ValueType类了),不过可以实现多个接口。

    1.2 初始化

    struct中的每个Field在其默认构造函数中都初始化为default(xxx)类型的值,C#完全禁止了用户显示定义默认构造器,也不能在声明时对Field进行赋值。如果要自己编写构造函数,则必须对每个Field赋值,不能只对其中一些Field赋值。

    1.3 存储

    structclass不同,数据存储在栈上,而不是堆上。

    二、与引用类型的转换

    2.1 装箱步骤(值类型转换为引用类型)

    1) 在堆中分配对应的内存空间

    2) 内存复制操作,栈上的数据复制到堆中

    3) 更新对象或接口引用,使之指向堆中的位置

    装箱操作往往是隐式转换中用到的。

    2.2拆箱操作(引用类型转换为值类型)

    拆箱会解除对堆中对象的引用。一般地,拆箱完毕后会有复制内存的操作。拆箱操作必须显式转换。另外,必须拆箱为其基础类型,见下面代码:

    int ia;
    object ob;
    double dc;
    ia
    =23;
    ob
    =ia;(装箱操作)
    /*InvalidCastException,拆箱操作只能返回int类型,应确保源类型能转换成目标类型*/
    //dc=(double)ob;
    dc=(double)(int)ob;//正确,要首先拆箱为基础类型

    三、开头代码的解释

    第一步以前:

    object objMv = mv;

    该步骤引发装箱操作,会在堆上产生一个mv的内存副本,然后objMv指向该副本

    第一步:

    objMv要调用structMethod1函数,就必须拆箱为struct。这样,就会产生一个栈上的临时副本,函数Method1对临时副本进行了更改,但是不会被保存在堆上的objMv中。所以堆上的objMvb值不会改变

    第二步:

    mv要调用接口IMyValueType的函数Method1,就会被装箱为IMyValueType。这样,就会产生一个堆上的临时副本,函数Method1对临时副本进行了更改,但是不会影响栈上mv的内存数据。

    第三步:

    引用类型之间的转换,没有装拆箱操作,不会有复制操作,即Method1可以更改objMv中的值

    四、总结

    装箱、拆箱有一些容易被人忽视的特性。就像开头代码一样,以为修改了structobject的值,其实根本没有修改,从而引发致命错误。记住“不要创建可变值类型”,就会在很大程度上避免其中的多数问题。

    Ps:有什么不足之处多多指教哈,谢谢~J

  • 相关阅读:
    v-if与v-show的区别
    vue 初始化项目
    element-ui的el-table中添加递增序号
    根据下拉框中选择的列在下面表格中显示对应的列
    element-ui中el-input需要改变placeholder的字体颜色
    根据不同路由,使对应的菜单高亮显示
    中间文字,两边横线的css3伪元素的使用
    关于正则表达式进行自定义校验
    对输入的表单内容自定义校验
    vue实现登录之后长时间未操作,退出登录
  • 原文地址:https://www.cnblogs.com/ganmuren/p/2137958.html
Copyright © 2020-2023  润新知