zoukankan      html  css  js  c++  java
  • C# 值类型 与引用类型

    前言

    一般来说,值类型存于栈,引用类型存在于堆,值类型转化为引用类型叫Box, 引用类型转为值类型在Unbox, 最近看了一本书发现值类型与引用类型的知识远不止这些。

    我发现在一下几点我的理解一直是错误的:

    错误1. struct是值类型,它与System.Object没有任何关系。

    struct 直接基类是System.ValueType, 而它的基类就是Sysem.Object, 而且int, bool,等基本类型都是struct. 所以所有的C#类型都继承自System.Object.

    错误2. 值类型转换为接口类型不会装箱

    我们都知道object obj = 3 会装箱; 其实IEquatable<int> obj = 3; 和前面的一样也会装箱。

    错误3. struct类型的equals 不会装箱。

    不仅有装箱,而且还有两次!

    错误4:装箱,拆箱的内存分布。

    装箱后,会重新分配堆,除了相关的值,还有函数表指针等,内存对齐等,还有一个漏掉的是指向这个装箱后对象的引用。

    正文

    namespace ConsoleApplication1
    {
        // System.ValueType -> System.Object
        struct Point2D
        {
            public int X { get; set; }
    
            public int Y { get; set; }
    
            // version 1
            public override bool Equals(object obj)
            {
                if (!(obj is Point2D)) return false;
                Point2D other = (Point2D)obj;
                return X == other.X && Y == other.Y;
            }
    
            // version 2
            public bool Equals(Point2D other)
            {
                return X == other.X && Y == other.Y;
            }
    
            // version 3
            public static bool operator ==(Point2D a, Point2D b)
            {
                return a.Equals(b);
            }
            public static bool operator !=(Point2D a, Point2D b)
            {
                return !(a == b);
            }
        }
    
        struct Point2D_V4 : IEquatable<Point2D_V4>
        {
            public int X { get; set; }
    
            public int Y { get; set; }
    
            // version 1
            public override bool Equals(object obj)
            {
                if (!(obj is Point2D_V4)) return false;
                Point2D_V4 other = (Point2D_V4)obj;
                return X == other.X && Y == other.Y;
            }
    
            // version 2
            public bool Equals(Point2D_V4 other)
            {
                return X == other.X && Y == other.Y;
            }
    
            // version 3
            public static bool operator ==(Point2D_V4 a, Point2D_V4 b)
            {
                return a.Equals(b);
            }
            public static bool operator !=(Point2D_V4 a, Point2D_V4 b)
            {
                return !(a == b);
            }
        }
    
        public class Employee
        {
            public string Name { get; set; }
            // the hashcode is base on the its cotent
            public override int GetHashCode()
            {
                return Name.GetHashCode();
            }
        }
    
    
        class Program
        {
            static void Main(string[] args)
            {
                // version 1
                Point2D a = new Point2D(), b = new Point2D();
                //object test = b; // box b from value type to reference type
                //a.Equals(b); // box a to invoke the virtual function Equals
    
                // if we have 10,000,000 points, we will do 20,000,000 box operation, on 32-bit system, one box will allocate 16 Bytes.
    
                // version 2
                // How to avoid boxing override the equals.
                //a.Equals(b); // no need box a or b this time.
    
                // version 3
                //bool isequal = a == b;// no boxing here.
    
                // it seems ok now, but there is a edge case will cause boxing at CLR generic. we need implement Iequatable
                //Point2D_V4 i = new Point2D_V4(), j = new Point2D_V4();
                //IEquatable<Point2D_V4> k = i;// box occurs here, value type to interface requires boxing.
    
                //i.Equals(j);// no box occurs
    
    
                // once boxing, they are not have relationship to orignal type. so the best practice is let the value type immutable, like system.datetime. 
                //Point2D_V4 point = new Point2D_V4 { X = 5, Y = 7 };
                //Point2D_V4 anotherPoint = new Point2D_V4 { X = 6, Y = 7 };
                //IEquatable<Point2D_V4> equatable = point; //boxing occurs here
                //equatable.Equals(anotherPoint); //returns false
                //point.X = 6;
                //point.Equals(anotherPoint); //returns true
                //equatable.Equals(anotherPoint); //returns false, the box was not modified!
    
    
                // then we need to talk about the hashcode, if you know about the hashkey in datastructure. the hashcode is used for identity one or more than one items.
                // here are some requirements to the hashcode function:
                //1. if two item is equal, the hashkey must be equal.
                //2. if two item is equal, the hashkey should not equal, but not must.
                //3. the GetHashCode function should fast.
                //4. the Hashcode should not change.
                HashSet<Employee> employees = new HashSet<Employee>();
                Employee kate = new Employee { Name = "Kate Jones" };
                employees.Add(kate);
                kate.Name = "Kate Jones-Smith";
    
                // it really shock me, I don't notice this before.
                bool has = employees.Contains(kate); //returns false!
    
            }
    
        }
    }

      version 1:

      public override bool Equals(object obj)

         首先参数ojbect 会做一次装箱,然后调用Equals 由于是虚函数,而值类型没有函数指针表,无法调用虚函数,必须转为引用类型,才能调用,我用ILDasm的确可以看到做了Box.

    Version 2:
    当我们重载了Equals函数,可以避免调用虚函数,这样可以避免两次装箱。

    version 3:

    让比较更加完善,对== , != 进行重载。

    version 4:
    继承自IEquatable, 在CLR generics 中会用到。需要进一步的验证。

    version 5:
    对HashCode的设计做了一些建议。不要依赖于可变的东西,否则正如上面例子所演示的,潜在的不稳定性。

    总结
    1. 使用值类型,当我们会创建大量的实例,比如10,000,000
    2. override Equals, oeverload Equals, implement IEquatable<T> , overload ==, !=,
    3. override GetHashCode
    4. 使值类型为不变


    参考

    <<Pro .Net Performance>>




  • 相关阅读:
    15. DML, DDL, LOGON 触发器
    5. 跟踪标记 (Trace Flag) 834, 845 对内存页行为的影响
    4. 跟踪标记 (Trace Flag) 610 对索引组织表(IOT)最小化日志
    14. 类似正则表达式的字符处理问题
    01. SELECT显示和PRINT打印超长的字符
    3. 跟踪标记 (Trace Flag) 1204, 1222 抓取死锁信息
    2. 跟踪标记 (Trace Flag) 3604, 3605 输出DBCC命令结果
    1. 跟踪标记 (Trace Flag) 1117, 1118 文件增长及空间分配方式
    0. 跟踪标记 (Trace Flag) 简介
    SpringBoot + Redis + Shiro 实现权限管理(转)
  • 原文地址:https://www.cnblogs.com/ming11/p/4541108.html
Copyright © 2011-2022 走看看