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




  • 相关阅读:
    爬虫流程
    康哥笔记
    csdn笔记
    数据库多表联查
    完整数据恢复
    Linux安装mysql
    linux在vm下实现桥接模式
    Linux下ntpdate时间同步
    spark集群在执行任务出现nitial job has not accepted any resources; check your cluster UI to ensure that worker
    flume在启动时出现brokerList must contain at least one Kafka broker
  • 原文地址:https://www.cnblogs.com/ming11/p/4541108.html
Copyright © 2011-2022 走看看