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,0002. override Equals, oeverload Equals, implement IEquatable<T> , overload ==, !=,3. override GetHashCode4. 使值类型为不变

参考

<<Pro .Net Performance>>

时间: 2024-10-20 09:29:33

C# 值类型 与引用类型的相关文章

定义类+类实例化+属性+构造函数+匿名类型var+堆与栈+GC回收机制+值类型与引用类型

为了让编程更加清晰,把程序中的功能进行模块化划分,每个模块提供特定的功能,而且每个模块都是孤立的,这种模块化编程提供了非常大的多样性,大大增加了重用代码的机会. 面向对象编程也叫做OOP编程 简单来说面向对象编程就是结构化编程,对程序中的变量结构划分,让编程更清晰. 类的概念: 类实际上是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法. 类定义了类的每个对象(称为实例)可以包含什么数据和功能. 类中的数据和函数称为类的成员:数据成员        函数成员 数据成员: 数据成员

Java值类型和引用类型

[定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值, 那么调用这个方法是传入的变量的值也将改变.值类型表示复制一个当前变量传给方法, 当你在这个方法中改变这个变量的值时,最初生命的变量的值不会变.通俗说法: 值类型就是现金,要用直接用:引用类型是存折,要用还得先去银行取现.----(摘自网上) [值类型] 也就是基本数据类型 基本数据类型常被称为四类八种 四类: 1,整型 2,浮点型 3,字符型4,逻辑型 八种: 1,整型3种 by

java 值类型和引用类型的区别

1. Java中值类型和引用类型的不同? [定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值, 那么调用这个方法是传入的变量的值也将改变.值类型表示复制一个当前变量传给方法, 当你在这个方法中改变这个变量的值时,最初生命的变量的值不会变.通俗说法: 值类型就是现金,要用直接用:引用类型是存折,要用还得先去银行取现.----(摘自网上) [值类型] 也就是基本数据类型 基本数据类型常被称为四类八种 四类: 1,整型 2,浮点型 3,字

c# 值类型与引用类型 值传递与引用传递

值类型与引用类型: 值类型 :1.值类型大小固定.存储在栈上.  2.不能继承,只能实现接口 3.派生自valuetype int double char float byte bool enum struct decimal 引用类型:1.在栈上存储了一个地址实际存储在堆中,大小不固定. 2.数组.类.接口.委托 string 数组 类 接口 委托 值传递与引用传递: 值类型按值传递.值类型按引用传递.引用类型按值传递.引用类型按引用传递. 值传递:默认传递都是值传递 ,把栈中内容拷贝一份引用

值类型和引用类型的区别

数据项的类型定义了存储数据需要的内存大小,组成该类型的数据成员以及该类型能执行的函数.类型还决了对象在内存中的存储位置-栈或堆. 类型被分为:值类型和引用类型,这两种类型的对象在内存中的存储方式不同.值类型只需要一段单独的内存,用于存储实际的数据,引用类型需要两段内存:第一段存储实际的数据,它总是位于堆中,第二段是一个引用(即内存地址,不懂是不是就是直接寻址),指向数据在堆中的存放位置. 如果数据不是其他类型的成员,是独立的,比如方法代码块声明的临时变量int age ;如果是某一类型的成员,,

C#中值类型和引用类型

本文将介绍C#类型系统中的值类型和引用类型,以及两者之间的一些区别.同时,还会介绍一下装箱和拆箱操作. 值类型和引用类型 首先,我们看看在C#中哪些类型是值类型,哪些类型是引用类型. 值类型: 基础数据类型(string类型除外):包括整型.浮点型.十进制型.布尔型. 整型(sbyte.byte.char.short.ushort.int.uint.long.ulong ) 浮点型(float 和 double ) 十进制型(decimal ) 布尔型(bool ) 结构类型(struct) 枚

04.C#类型系统、值类型和引用类型(二章2.2-2.3)

今天要写的东西都是书中一些概念性的东西,就当抄笔记,以提问对话的方式将其写出来吧,说不定以后面试能有点谈资~~~ Q1.C#1系统类型包含哪三点特性? A1.C#1类型系统是静态的.显式的和安全的. Q2.为什么称为静态类型? A2.静态类型是用来描述表达式在编译时的类型,当声明一个类型的变量时,不能将变量指向其它类型的对象. Q3.显式类型和隐式类型的区别? A3.显式类型和隐式类型只有静态类型中的语言才有意义.显式类型需要显式声明一个变量的类型,而隐式类型则将类型的判断责任推给编译器,但是在

现金与存折---值类型和引用类型

在软考的时候也接触过值类型和引用类型,那时候应付做题还是可以的,可是考完之后再突然面对这两个词汇,又觉得迷茫无措了.现在想想,还是实践吧,当时只是简单的了解了其原理,没有用代码来实现,所以只能算是初步的,暂时的了解.这篇文章就是为了弥补初步的遗憾,进行深一步的学习. 理论联系实践,才是对现实的超越.就像门和钥匙一样,完美结合才有防窃和安全之功效.所以,该篇文章的主要思路也是从理论和实践两个方面分别对"值类型和引用类型"进行详细阐述. --------------------------

【转】C#详解值类型和引用类型区别

通用类型系统 值类型 引用类型 值类型和引用类型在内存中的部署 1 数组 2 类型嵌套 辨明值类型和引用类型的使用场合 5 值类型和引用类型的区别小结 首先,什么是值类型,什么是引用类型? 在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中. 值类型(value type):byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型.值类型变量声明后,不管是否已经赋值,编译器为其分配内

C# 类型基础 值类型和引用类型

引言 本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone其实也就是对象复制.复制又分为了浅度复制(Shallow Copy)和深度复制(Deep Copy),浅度复制和深度复制又是以如何复制引用类型成员来划分的.由此又引出了引用类型和值类型,以及相关的对象判等.装箱.拆箱等基础知识.索性从最基础的类型开始自底向上写起. 值类型和引用类型 先简单回顾一下C#中的类型系统.C# 中的类型一共分为