值类型不是值类型(ValueType is NOT a Value Type):闲谈.Net类型

简介:

 .Net的类型系统比较复杂,很多人经常给绕进来,比如《[原创]慢话interface是值类型还是引用类型》一文。而网上的、书上的关于.Net类型的表述一般是错误的或者不完全的,准确性最高的是MSDN上的表述,但那个表述又太简单了,让人很难理解。本文试着通俗的解释几个关于.Net类型的基础问题。

1. 托管与非托管

 像现实生活中,有体制内和体制外之分,.Net 里也有托管和非托管之分。托管堆、GC这些是托管部分,非托管堆是非托管部分。
 托管部分叫做组织,体制内,非托管部分叫做群众,体制外。

 

2. 引用类型是体制内的类型
 有组织关系的,体制内的类型叫做引用类型。体制内的类型(引用类型)有什么特点呢?它不能脱离组织。由组织 new 它,死后魂归组织。生命周期由组织来维护。生是组织的人,死是组织的鬼。它是受组织保护的,你不能用指针这样粗暴野蛮危险的东西来威胁体制内的类型。
 
3. 值类型是体制外的类型

 体制外的类型是值类型(非托管堆 或者 栈中)。
 值类型要想进入体制内(托管堆),必须找到挂靠者。挂靠者必须是体制内的类型。这像贷款要担保一样,办杂志要找到托管单位一样,进入体制类,必须找到一个管理者。所以,一个值类型要想进入托管堆里,它必须是一个引用类型组成部分或者组成部分的组成部分……,它的生死存亡,和这个引用类型绑定在一起。

 装箱是一种特殊的挂靠。
 体制外的类型不受体制保护,所以可以用指针这样粗暴野蛮危险的东西来处置。
 
4.值类型和引用类型的区别
 引用类型和值类型的区别是有没有组织关系,是体制内的还是体制外的,而不是常说的什么栈啊堆啊的blablabla。
 引用类型是体制内的,一生都在体制内呆着。而值类型是体制外的人,平时在栈里和非托管堆里生存,想进体制内得挂靠体制内的人。
 由于身份的不同,所受的待遇也不同。这就好比,一个群众和一个官员去办事,所受到的待遇不同一个道理。
 

5.办事大厅——栈

 办事(函数调用)在栈里进行。

 

6.办事待遇

 体制内的和体制外的办事待遇(传参)不一样。

 组织里的人办事只用打电话就行了,不用亲自去。当然,他也没办法亲自去,他的脑袋、胳膊、腿都是组织的,都在托管堆上,托管堆不会放他走,他根本去不了。而体制外的人办事得亲自去。当然也可以不亲自去,但是得特殊处理(ref,out,或者指针)。

 

7.为什么要分体制内和体制外
 像java都是体制内的该多好啊。.net 说不,要进行体制改革,让大量的个头小的类型变成体制外的,不然的话体制负担就太重了。
 
8. 接口是什么
 接口是一个逻辑上的概念,是对类型行为的规范和约束。比如欧盟不承认中国的市场经济地位,他们觉得中国没有实现“市场经济地位”的一些规范。
 
9. 接口的实现
 逻辑上的东西只落在头脑中和纸面上,而在现实中,逻辑上的东西是需要实现来保证的接口有很多种实现方式。比如,C++中根本没提供接口的具体实现,那么经常将抽象类当接口用。再比如,haXe的typedef 实际上也是接口的一种实现,它用于编译时类型检测。
 
10. .Net 中接口的实现方案
 接口有很多种实现方案,.Net选择了一种自己认为合理的一种:用引用类型来实现接口。
 当然,.Net完全可以把接口实现为值类型,或者值类型与引用类型之外的第三种类型。而.Net既然把它实现为引用类型,我们就得接受这个观点,受它的约束。

 

11. 根据10,接口是引用类型,是体制内的类型

 

12. 从值类型转换为它所实现的接口类型要装箱

复制代码
public  struct Person : IPerson
{
     public Int32 Age;
     public  void SetAge( int age)
    {
        Age = age;
    }
 
     public Int32 GetAge()
    {
         return Age;
    }
}
 
public  interface IPerson
{
     void SetAge( int age);
    Int32 GetAge();
}
 
Person p =  new Person();
p.SetAge( 5);
Console.WriteLine(p.Age);
IPerson ip = p;
ip.SetAge( 8);
Console.WriteLine(ip.GetAge());
Console.WriteLine(p.GetAge());
复制代码

上面的代码输出结果是:

5
8
5

 

13. 在特殊情况下,接口是性能杀手

 操作100万个值类型和将100万个值类型转换为引用类型再进行操作,性能大不一样。这时切忌为了抽象为了代码的优美,而用接口来操作值类型,性能会大幅度下降,GC也会表示压力很大。我就犯过这个错误,将图像的像素实现某个接口,用接口来操作像素。

 

14. 白条现象

 不要认为某值类型实现了接口,它就“是”接口。实际上,它装箱后才是接口。这就好比打白条,白条可以转换为现金,但白条和现款是两码事。
 而从12的测试结果看,值类型和接口类型是两码事。
 一个值类型“实现”接口,意思是,这个值类型可以转换为该接口类型。

 

15. 绕口令:值类型不是值类型

 typeof(ValueType).IsValueType == false 

 System.ValueType 是引用类型。
 
16. 值类型隐式继承自System.ValueType 类型的意思是值类型可以转换为System.ValueType 类型,转换过程中有装箱现象。这也是一种“白条”现象。

 在 12 的代码基础上进行下列测试:

            Person p = new Person();
            p.SetAge(5);          

            Console.WriteLine(p.Age);
            IPerson ip = p;
            ip.SetAge(8);
            ValueType vt = p;
            Person vtp = (Person)vt;
            vtp.SetAge(9);
            Console.WriteLine(ip.GetAge());
            Console.WriteLine(p.GetAge());           

            Console.WriteLine(vtp.GetAge());

结果是:

5
8
5
9

 

17. 所有类型隐式继承自Object类型是他们可以转换为Object 类型,当然,值类型转换过程中也有装箱现象。这也是一种“白条”现象。

            Person p = new Person();
            p.SetAge(5);
            Console.WriteLine(p.Age);
            IPerson ip = p;
            ip.SetAge(8);
            ValueType vt = p;
            Object obj = p;
            Person vtp = (Person)vt;
            Person objp = (Person)p;
            vtp.SetAge(9);
            objp.SetAge(12);
            Console.WriteLine(ip.GetAge());
            Console.WriteLine(p.GetAge());
            Console.WriteLine(vtp.GetAge());
            Console.WriteLine(objp.GetAge());

结果是:

5
8
5
9
12

 

18. END

本文转自xiaotie博客园博客,原文链接http://www.cnblogs.com/xiaotie/archive/2012/09/08/2676450.html如需转载请自行联系原作者


xiaotie 集异璧实验室(GEBLAB)

相关文章
|
5月前
|
存储 开发框架 .NET
"揭秘.NET内存奥秘:从CIL深处窥探值类型与引用类型的生死较量,一场关于速度与空间的激情大戏!"
【8月更文挑战第16天】在.NET框架中,通过CIL(公共中间语言)可以深入了解值类型与引用类型的内存分配机制。值类型如`int`和`double`直接在方法调用堆栈上分配,访问迅速,生命周期随栈帧销毁而结束。引用类型如`string`在托管堆上分配,堆栈上仅存储引用,CLR负责垃圾回收,确保高效且自动化的内存管理。
58 6
|
5月前
|
存储 C#
揭秘C#.Net编程秘宝:结构体类型Struct,让你的数据结构秒变高效战斗机,编程界的新星就是你!
【8月更文挑战第4天】在C#编程中,结构体(`struct`)是一种整合多种数据类型的复合数据类型。与类不同,结构体是值类型,意味着数据被直接复制而非引用。这使其适合表示小型、固定的数据结构如点坐标。结构体默认私有成员且不可变,除非明确指定。通过`struct`关键字定义,可以包含字段、构造函数及方法。例如,定义一个表示二维点的结构体,并实现计算距离原点的方法。使用时如同普通类型,可通过实例化并调用其成员。设计时推荐保持结构体不可变以避免副作用,并注意装箱拆箱可能导致的性能影响。掌握结构体有助于构建高效的应用程序。
154 7
|
6月前
|
开发框架 .NET API
.NET Core 和 .NET 标准类库项目类型有什么区别?
在 Visual Studio 中,可创建三种类库:.NET Framework、.NET Standard 和 .NET Core。.NET Standard 是规范,确保跨.NET实现的API一致性,适用于代码共享。.NET Framework 用于特定技术,如旧版支持。.NET Core 库允许访问更多API但限制兼容性。选择取决于兼容性和所需API:需要广泛兼容性时用.NET Standard,需要更多API时用.NET Core。.NET Standard 替代了 PCL,促进多平台共享代码。
|
8月前
|
安全 API C#
C#.Net筑基-类型系统②常见类型--枚举Enum
枚举(enum)是C#中的一种值类型,用于创建一组命名的整数常量。它们基于整数类型(如int、byte等),默认为int。枚举成员可指定值,未指定则从0开始自动递增。默认值为0。枚举可以与整数类型互相转换,并可通过`[Flags]`特性表示位域,支持位操作,用于多选场景。`System.Enum`类提供了如`HasFlag`、`GetName`等方法进行枚举操作。
|
8月前
|
编译器 C#
C#.Net筑基-类型系统②常见类型 --record是什么类型?
`record`在C#中是一种创建简单、只读数据结构的方式,常用于轻量级数据传输。它本质上是类(默认)或结构体的快捷形式,包含自动生成的属性、`Equals`、`ToString`、解构赋值等方法。记录类型可以继承其他record或接口,但不继承普通类。支持使用`with`语句创建副本。例如,`public record User(string Name, int Age)`会被编译为包含属性、相等比较和`ToString()`等方法的类。记录类型提供了解构赋值和自定义实现,如密封的`sealed`记录,防止子类重写。
|
8月前
|
存储 C#
C#.Net筑基-类型系统②常见类型--结构体类型Struct
本文介绍了C#中的结构体(struct)是一种用户自定义的值类型,适用于定义简单数据结构。结构体可以有构造函数,能定义字段、属性和方法,但不能有终结器或继承其他类。它们在栈上分配,参数传递为值传递,但在类成员或包含引用类型字段时例外。文章还提到了`readonly struct`和`ref struct`,前者要求所有字段为只读,后者强制结构体存储在栈上,适用于高性能场景,如Span和ReadOnlySpan。
|
8月前
|
存储 安全 Unix
C#.Net筑基-类型系统②常见类型--日期和时间的故事
在System命名空间中,有几种表示日期时间的不可变结构体(Struct):DateTime、DateTimeOffset、TimeSpan、DateOnly和TimeOnly。DateTime包含当前本地或UTC时间,以及最小和最大值;DateTimeOffset增加了时区偏移信息,适合跨时区操作。UTC是世界标准时间,而格林尼治标准时间(GMT)不稳定,已被更精确的UTC取代。DateTimeOffset和DateTime提供了转换为UTC和本地时间的方法,以及各种解析和格式化函数。
|
7月前
|
存储 编译器
【.NET Core】可为null类型详解
【.NET Core】可为null类型详解
233 0
|
开发框架 安全 前端开发
一个高性能类型安全的.NET枚举实用开源库
一个高性能类型安全的.NET枚举实用开源库
90 0