【CLR Via C#笔记】 值类型与拆装箱、参数传递
1. 值类型都是从 System.ValueType继承的,并且都是Sealed。无法再次被继承。
在Reflector中查看ValueType原型如下,重写了Equals, ToString,GetHashCode.
因而在调用这些方法的时候,无需进行装箱操作:
public abstract class ValueType
{
// Methods
protected ValueType();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool CanCompareBits(object obj);
public override bool Equals(object obj);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool FastEqualsCheck(object a, object b);
[MethodImpl(MethodImplOptions.InternalCall)]
public override extern int GetHashCode();
public override string ToString();
}
{
return base.GetType().ToString();
}
以System.Byte为例:
{
return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);
}
public static extern string FormatInt32(int value, string format, NumberFormatInfo info);
可以看到Byte的ToString方法参数中已经使用的是值类型参数(this)。
而对于GetType或MemberwiseClone。是从Object集成的非虚方法。这些方法期望this参数是指向堆上对象的一个指针。
因此在调用时需要进行装箱操作
public class Object
{
// Methods
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public Object();
public virtual bool Equals(object obj);
public static bool Equals(object objA, object objB);
private void FieldGetter(string typeName, string fieldName, ref object val);
private void FieldSetter(string typeName, string fieldName, object val);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
protected override void Finalize();
private FieldInfo GetFieldInfo(string typeName, string fieldName);
public virtual int GetHashCode();
[MethodImpl(MethodImplOptions.InternalCall)]
public extern Type GetType();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool InternalEquals(object objA, object objB);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int InternalGetHashCode(object obj);
[MethodImpl(MethodImplOptions.InternalCall)]
protected extern object MemberwiseClone();
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static bool ReferenceEquals(object objA, object objB);
public virtual string ToString();
}
2. 值类型(比如结构体)中定义的成员不应修改类型的任何实例字段。
下面的代码演示了在值类型中提供更改字段的方法后,在调用中容易出现问题
{
internal interface IChangeBoxedPoint
{
void Change(Int32 _x, Int32 _y);
}
struct Point : IChangeBoxedPoint
{
private Int32 X, Y;
public Point(Int32 _x, Int32 _y)
{
this.X= _x;
this.Y = _y;
}
public void Change(Int32 _x, Int32 _y)
{
this.X = _x;
this.Y = _y;
}
public override string ToString()
{
return string.Format("({0},{1})",X,Y);
}
}
static void Main(string[] args)
{
Point p = new Point(1, 1);
Console.WriteLine(p);
p.Change(2, 2);
Console.WriteLine(p);
//装箱
Object o = p;
Console.WriteLine(o);
//改变的是拆箱后 位于堆栈上的 值类型实例,而o存储的装箱后(堆上)的地址
((Point)o).Change(3, 3);
Console.WriteLine(o);
//同上
((IChangeBoxedPoint)p).Change(4, 4);
Console.WriteLine(p);
//Object 和 interface都是引用类型,此处没有拆装箱操作
((IChangeBoxedPoint)o).Change(5, 5);
Console.WriteLine(o);
Console.ReadKey();
}
}
执行结果为:
(2,2)
(2,2)
(2,2)
(2,2)
(5,5)
上述第3个和第4个并没有输出(3,3)(4,4)
这是因为改变的是拆箱之后的堆栈中的值类型,而原装箱的引用不会变化。
不仔细分析很难看出正确的结果,当然没必要时刻当心这个问题,只需将struct 改成class后,一切都清晰多了。
3. 参数传递
c#中函数参数传递包括传值和传址类型。对于值类型,传值将复制一份值类型的Copy并传递给函数参数,而传值将把值类型在堆栈上的存储地址传递给函数参数;
而对于引用类型,传值将复制引用类型在堆栈上的引用(指针),该copy和原引用类型指向同一对象,因此也可以修改对象内容,
而传址将传递该引用类型在堆栈上指针的Copy,因此可以修改对象本身包括新创对象。
Q:传址方式的两种out 和ref 有什么区别和联系?
A:1. 从CLR和IL的角度看,2者是一致的:都生成对被传递内容的指针。
2. 关键区别在于编译器保证代码的正确性,在对引用类型的传递时,out 传递需要保证函数过程中实例化,而ref 将检查传入引用是否已经实例化。
3. 只存在与out 和 ref 差异的重载是不合法的。
Q: 引用类型按值传递也能在函数中改变对象内容,字符串是引用类型,为什么表现得和值类型差不多?
A: 先看下列代码:
class A
{
public int i = 2;
override String ToString()
{
return i.ToString();
}
}
f (A a)
{
a.i = 1;
}
f(Int32 i)
{
i = 1;
}
F(String s)
{
s = "1";
}
Main()
{
A a1 = new A();
Int 32 i1 = 2;
String s1 = "2";
Console.WriteLine(a1); //Display 2
Console.WriteLin(i1); //Display 2
Console.WriteLin(s1); //Display 2
f(a1);
f(i1);
f(s1);
Console.WriteLine(a1); //Display 1
Console.WriteLin(i1); //Display 2
Console.WriteLin(s1); //Display 2
}
从结果看,String类型和Int32类型表现得差不多。
实际上这是因为字符串对象的不可改变性造成的,即我们不能改变String类型在堆中的内容,每次赋值其实是在堆中重新创建一个String对象,并将新地址赋给原引用。
按值传递字符串时,原引用s1和形参s指向堆中同一字符串对象,对s赋值时,堆中将新建内容为1的字符串对象,并且s将指向它。但这并不会对原引用s1造成任何影响。
4. params关键字实现可变参数传递
void f( params Int32[] values){
foreach (Int32 i in values) ...
}
调用:f();f(1);f(1,2);f(3,4,2,23,3)
由于数值对象在堆中分配,最终需要垃圾收集器回收,使用params会导致一些额外的开销,最好多定义几个常用的重载。如:
f(Int32 i)
f(Int32 i1,Int32 i2)
转载于:https://www.cnblogs.com/calmzeal/archive/2008/10/28/1321478.html
【CLR Via C#笔记】 值类型与拆装箱、参数传递相关推荐
- 《CLR via C#》读书笔记 之 基元类型、引用类型和值类型
第五章 基元类型.引用类型和值类型 2013-02-27 5.3 值类型的装箱和拆箱 5.3.2 对象的相等性和同一性 参考 ToDo: 什么时候使用值类型,什么时候使用引用类型 5.3 值类 ...
- .NET六大剑客:栈、堆、值类型、引用类型、装箱和拆箱
.NET六大剑客:栈.堆.值类型.引用类型.装箱和拆箱 一."堆","栈"专区 这两个字我相信大家太熟悉了,甚至于米饭是什么?不知道..."堆&quo ...
- 引用类型和值类型学习笔记
一.基本概念 CLR支持两种类型,值类型和引用类型.它们从类型的定义.实例的创建.参数传递.到内存的分配都有所不同:.NET中的类型分类如下: 值类型和引用类型最本质的区别在于内存的分布上,大致可以这 ...
- .net框架读书笔记---基础类型
接上一篇.net框架读书笔记---值类型的装箱与拆箱, 一.Object CLR要求每个类型都最终集成自System.Object类型,这意味着以下两种定义是相同的: //隐式继承自Object cl ...
- 第五章 基元类型,引用类型和值类型
目录 5.1 编程语言的基元类型 5.2 引用类型和值类型 5.3 值类型的装箱和拆箱 5.4 对象哈希码 5.5 dynamic基元类型 5.1 编程语言的基元类型 编译器直接支持的数据类型称为基元 ...
- 述说C#中的值类型和引用类型的千丝万缕
关于值类型和引用类型方面的博客和文章可以说是汗牛充栋了,今天无意中又复读了一下这方面的知识,感觉还是有许多新感悟的,就此时间分享一下: CLR支持两种类型:值类型和引用类型,看起来FCL的大多数类型是 ...
- c#值类型和引用类型
值类型:整型.布尔型.字符型.实数型.结构型.枚举型. 引用类型:类.对象.字符串.数组.接口.委托. 区别: 1.值类型通常被分配在栈上,它的变量直接包含变量的实例,使用效率比较高. 2.引用类型分 ...
- [你必须知道的.NET]第九回:品味类型---值类型与引用类型(中)-规则无边
发布日期:2007.5.28 作者:Anytao ©2007 Anytao.com ,原创作品,转贴请注明作者和出处. 接上回[第八回:品味类型---值类型与引用类型(上)-内存有理]的探讨,继续我们 ...
- 值类型和引用类型的区别,应该很全的。
区别: 1.值类型通常被分配在栈上,它的变量直接包含变量的实例,使用效率比较高. 2.引用类型分配在托管堆上,引用类型的变量通常包含一个指向实例的指针,变量通过该指针来引用实例. 3.值类型继承自Va ...
- 关于.net中值类型的方法调用
最近在看关于box和unbox的内存分配问题,发现一旦值类型调用了基类的方法或接口的时候就会发生装箱操作.因为基类型的方法或接口必须通过TypeHandle获得.由此引出了一个问题.若所执行的方法并不 ...
最新文章
- OpenCV持久化(二)
- Science重磅:DeepMind再获突破,用AI开启理解电子相互作用之路
- NIO中的SelectionKey
- 掉一根头发,搞定二叉排序(搜索)树
- 前端学习(1370):错误处理中间件
- IDEA 创建 SpringBoot 项目
- CSS 奇技淫巧:动态高度过渡动画
- Android开发笔记(一百六十八)为应用绑定通知渠道并展示消息角标
- ASP.NET 首页性能的4大做法
- draw9patch做一个中心不变形的图片
- tensorrt 分割_超多,超快,超强!百度飞桨发布工业级图像分割利器PaddleSeg
- java web服务器cpu占用过高的处理 (2014-07-21 17:17:36)
- eclipse换炫酷主题
- Linux系统CPU占用100%原因分析
- Linux系统日志分析与管理
- maxlength中文和英文html,让input maxlength区分中英文
- python可以这样学豆瓣_用python爬取豆瓣短评,这是我见过最牛逼的教程!
- 基于ONNX人脸识别实例(SCRFD/ArcFace)-C#版
- Win10Chrome调试安卓Chrome
- linux查看群组所属用户,linux 列出用户所属的所有群组的5种方法
热门文章
- 什么是Tensor Flow和lite以及数据流图
- Const限定符与C++11Constexpr的区别
- 神经损伤怎么康复好 成都顾连康复医院专科专治
- Spring Cloud Spring Boot mybatis 企业分布式微服务云(五)服务消费(Feign)【Dalston版】...
- VSCode好用的Python插件及配置
- 《Windows 8 权威指南》——2.10 几招解决Windows 8 Metro应用打不开的问题
- MongoDB学习笔记~官方驱动的原生Curd操作
- 「我们的首要之务,并不是遥望模糊的远方,而是专心处理眼前的事务。」---这是卡内基先生所强调的克服忧虑、开创人生的关键。...
- SIP Trunk / SIP 中继服务
- Python热门开源项目TOP10