# 全面解析C#中参数传递
## **一、引言**
对于一些初学者(包括工作几年的人在内)来说,有时候对于方法之间的参数传递的问题感觉比较困惑的,因为之前在面试的过程也经常遇到参数传递的基础面试题,这样的面试题主要考察的开发人员基础是否扎实,对于C#中值类型和引用类型有没有深入的一个理解——这个说的理解并不是简单的对它们简单一个定义描述,而在于它们在内存中分布。所以本文章将带领大家深入剖析下C#中参数传递的问题,并分享我自己的一个理解,只有你深入理解了才能在不运行程序的情况就可以分析出参数传递的结果的。
## 二、按值传递
对于C#中的参数传递,根据参数的类型可以分为四类:
* **值类型参数的按值传递**
* **引用类型参数的按值传递**
* **值类型参数的按引用传递**
* **引用类型参数的按引用传递**
然而在默认情况下,CLR方法中参数的传递都是按值传递的。为了帮助大家全面理解参数的传递,下面就这四种情况一一进行分析。
## 2.1 值类型参数的按值传递
对于参数又分为:形参和实参,形参指的是被调用方法中的参数,实参指的是调用方法的参数,下面结合代码帮助大家理解形参和实参的概念:
```
class Program
{
static void Main(string[] args)
{
int addNum = 1;
// addNum 就是实参,
Add(addNum);
}
// addnum就是形参,也就是被调用方法中的参数
private static void Add(int addnum)
{
addnum = addnum + 1;
Console.WriteLine(addnum);
}
}
```
对于值类型的按值传递,传递的是该值类型实例的一个拷贝,也就是形参此时接受到的是实参的一个副本,被调用方法操作是实参的一个拷贝,所以此时并不影响原来调用方法中的参数值,为了证明这点,看看下面的代码和运行结果就明白了:
```
class Program
{
static void Main(string[] args)
{
// 1\. 值类型按值传递情况
Console.WriteLine("按值传递的情况");
int addNum = 1;
Add(addNum);
Console.WriteLine(addNum);
Console.Read();
}
// 1\. 值类型按值传递情况
private static void Add(int addnum)
{
addnum = addnum + 1;
Console.WriteLine(addnum);
}
```
运行结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb2e3bd90.jpg)
从结果中可以看出addNum调用方法之后它的值并没有改变,Add 方法的调用只是改变了addNum的副本addnum的值,所以addnum的值修改为2了。然而我们的分析到这里并没有结束,为了让大家深入理解传递传递,我们有必要知道为什么值类型参数的按值传递不会修改实参的值,相信下面这张图可以解释你所有的疑惑:
![](https://box.kancloud.cn/2016-01-23_56a2eb2e4b8a4.png)
## 2.2 引用类型参数的按值传递
当传递的参数是引用类型的时候,传递和操作的是指向对象的引用(看到这里,有些朋友会觉得此时不是传递引用吗?怎么还是按值传递了?对于这个疑惑,此时确实是按值传递,此时传递的对象的地址,传递地址本身也是传递这个地址的值,所以此时仍然是按值传递的),此时方法的操作就会改变原来的对象。对于这点可能看文字描述会比较难理解下面结合代码和分析图来帮助大家理解下:
```
class Program
{
static void Main(string[] args)
{
// 2\. 引用类型按值传递情况
RefClass refClass = new RefClass();
AddRef(refClass);
Console.WriteLine(refClass.addnum);
}
// 2\. 引用类型按值传递情况
private static void AddRef(RefClass addnumRef)
{
addnumRef.addnum += 1;
Console.WriteLine(addnumRef.addnum);
}
}
class RefClass
{
public int addnum=1;
}
```
运行结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb2e5b751.png)
为什么此时传递引用就会修改原来实参中的值呢?对于这点我们还是参数在内存中分布图来解释下:
![](https://box.kancloud.cn/2016-01-23_56a2eb2e693e6.png)
## 2.3 .String引用类型的按值传递的特殊情况
对于String类型同样是引用类型,然而对于string类型的按值传递时,此时引用类型的按值传递却不会修改实参的值,可能很多朋友对于这点很困惑,下面具体看看下面的代码:
```
class Program
{
static void Main(string[] args)
{
// 3\. String引用类型的按值传递的特殊情况
string str = "old string";
ChangeStr(str);
Console.WriteLine(str);
}
// 3\. String引用类型的按值传递的特殊情况
private static void ChangeStr(string oldStr)
{
oldStr = "New string";
Console.WriteLine(oldStr);
}
}
```
运行结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb2e7b905.png)
对于为什么原来的值没有被改变主要**是因为string的“不变性”,所以在被调用方法中执行 oldStr="New string"代码时,此时并不会直接修改oldStr中的"old string"值为"New string",因为string类型是不变的,不可修改的,此时内存会重新分配一块内存,然后把这块内存中的值修改为 “New string”,然后把内存中地址赋值给oldStr变量,所以此时str仍然指向 "old string"字符,而oldStr却改变了指向,它最后指向了 "New string"字符串**。所以运行结果才会像上面这样,下面内存分布图可以帮助你更形象地理解文字表述:
![](https://box.kancloud.cn/2016-01-23_56a2eb2e89978.png)
## 三、按引用传递
不管是值类型还是引用类型,我们都可以使用ref 或out关键字来实现参数的按引用传递,然而按引用进行传递的时候,需要注意下面两点:
方法的定义和方法调用都必须同时显式使用ref或out,否则会出现编译错误
CLR允许通过out 或ref参数来实现方法重载。如:
```
#region CLR 允许out或ref参数来实现方法重载
private static void Add(string str)
{
Console.WriteLine(str);
}
// 编译器会认为下面的方法是另一个方法,从而实现方法重载
private static void Add(ref string str)
{
Console.WriteLine(str);
}
#endregion
```
按引用传递可以解决由于值传递时改变引用副本而不影响引用本身的问题,此时传递的是引用的引用(也就是地址的地址),而不是引用的拷贝(副本)。下面就具体看看按引用传递的代码:
```
class Program
{
static void Main(string[] args)
{
#region 按引用传递
Console.WriteLine("按引用传递的情况");
int num = 1;
string refStr = "Old string";
ChangeByValue(ref num);
Console.WriteLine(num);
changeByRef(ref refStr);
Console.WriteLine(refStr);
#endregion
Console.Read();
}
#region 按引用传递
// 1\. 值类型的按引用传递情况
private static void ChangeByValue(ref int numValue)
{
numValue = 10;
Console.WriteLine(numValue);
}
// 2\. 引用类型的按引用传递情况
private static void changeByRef(ref string numRef)
{
numRef = "new string";
Console.WriteLine(numRef);
}
#endregion
}
```
运行结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb2e98ecb.png)
从运行结果可以看出,此时引用本身的值也被改变了,通过下面一张图来帮忙大家理解下按引用传递的方式:
![](https://box.kancloud.cn/2016-01-23_56a2eb2ea5f49.png)
## 四、总结
到这里参数的传递所有内容就介绍完了。总之,**对于按值传递,不管是值类型还是引用类型的按值传递,都是传递实参的一个拷贝,只是值类型时,此时传递的是实参实例的一个拷贝(也就是值类型值的一个拷贝),而引用类型时,此时传递的实参引用的副本。对于按引用传递,传递的都是参数地址,也就是实例的指针。**
所有源码下载:[参数传递](http://files.cnblogs.com/zhili/%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92.zip)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结