前言

菜鸟去重复之Sql的问题还没有得到满意的答案。如果哪位大哥有相关的资料解释,能够分享给我,那就太谢谢了。

接触C#一年了,感觉很多东西还是很模糊,像C#中的委托和事件

有些东西看多了不用也还是不会。还有些东西用多了不想也还是不精。

这次发现一篇解除我对于C#里面参数传递困惑的详细条例文章,忍不住翻译留存以备回顾。

英文好的可以直接点此处看原文了。MSDN相关解释链接在此处

前奏:引用类型

在C#中有两种常用的类型:引用类型和值类型。他们表现不同,很多人在使用他们的时候都感到了困惑,这里简单解释下他们的区别:

引用类型指引用类型的变量存储对实际数据的引用。如下代码:

StringBuilder sb = new StringBuilder();

这里我们定义了一个变量sb,创建一个新的StringBuilder对象,并把这个对象的引用赋给sb。sb实际存储的值不是该对象,只是它的引用。

引用类型之间的赋值只是简单地将其表达式或者变量赋给对应的变量。再看如下代码:

StringBuilder first = new StringBuilder();
first.Append("hello");
StringBuilder second = first;
Console.WriteLine(second); // Prints hello

这里我们定义了一个变量first,创建了一个新的StringBuilder对象,并把这个对象的引用赋给first。然后我们将first赋给second。

这意味着他们都指向了同一个对象。如果我们通过使用first.Append的方式修改该对象的内容,second也同样变化了。如下:

StringBuilder first = new StringBuilder();
first.Append("hello");
StringBuilder second = first;
first.Append(" world");
Console.WriteLine(second); // Prints hello world

虽然会这样,但他们仍然应该被看作相互独立的变量。修改first指向一个完全不同的对象(或者直接将null赋给它)完全不会影响second。

StringBuilder first = new StringBuilder();
first.Append("hello");
StringBuilder second = first;
first.Append(" world");
first = new StringBuilder("goodbye");
Console.WriteLine(first); // Prints goodbye
Console.WriteLine(second); // Still prints hello world

类(class),接口(interface),委托(delegate)和数组(array)都是引用类型。

前奏:值类型

引用类型在变量与真实数据之间有个间接层,值类型却不是。值类型变量直接存储其数据。

值类型之间赋值是将其值拷贝一份然后赋给对应的变量。下面用一个结构类型来解释下:

public struct IntHolder
{
public int i;
}

IntHolder是个结构体的值类型,它包含一个单独的整型i。对其赋值将会拷贝,如下所示:

IntHolder first = new IntHolder();
first.i = ;
IntHolder second = first;
first.i = ;
Console.WriteLine (second.i); //输出5

这里second.i值为5是因为当second=first时second.i保存了first.i的拷贝。自此,second是与first相互独立的。

所以即使后来first.i=6,second.i依然不变。

简单的类型(例如float,int,char),enum类型和struct类型都是值类型。

Note:很多类型(例如string)是引用类型,但表现的却像是值类型。这些是不可变类型,就是这些类型的实例一旦创建就不能再改变。

这使得引用类型在一些方面表现得值类型一样——尤其是当你使用一个指向是不可变的对象的引用时,

你就不用担心把它传递到一个方法里面或者从某个方法返回后值不对。

无论如何,你总是知道这个变量引用的对象的内容。这也是为什么string.Replace不改变该string的值,而是返回一个新的string实例。

如果string改变了,通过其它所有指向它的引用获取到的string也改变了,那明显不是我们期望的。

用一个可变的引用类型对比(例如ArrayList)加深大家理解。

如果一个方法的返回值为保存在变量中的一个ArrayList类型的引用,在方法内部并没有创建新的实例直接使用,

而是对一个已有的实例进行增加等一些操作,其它人员却不知道,这就可能出现问题。

之前说过了不可变的引用类型表现得像值类型,但实际不是值类型,大家千万不要被其表面迷惑,理解其实质,才能更好地运用。

测试你的理解

如果之前的IntHolder不是结构类型而是类,那输出的结果是多少呢?如果你不理解为什么是6,那估计是我翻译的有问题,

或者我翻译得不够清楚,实在罪过。如果您有好的建议,请告知我,不胜感激。

这里再次奉上原文链接,以备有人实在看不下去我过烂的翻译。

间奏:不同类型的参数

在C#中有四种不同类型的参数:值类型参数(默认),引用类型(ref),out类型,参数数组(params)。

你可以同时使用值类型和引用类型参数。

当你使用这些参数的时候,你应该在脑海里对“值类型”和“引用类型”有非常清晰的认识。

这样不管是在使用它们还是与它们相关的类型的时候,你都会感觉非常轻松。

值类型参数

C#中默认参数是值类型参数,这意味着在方法成员声明时会为其变量会创建一份拷贝,它就是你在方法内部调用指定的变量的初始值。

如果你改变这个值,不会对调用中传的值源造成任何影响。看下代码:

void Foo (StringBuilder x)
{
x = null;
} ... StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (y);
Console.WriteLine (y==null);// 输出False

y的值没有改变只是因为x被赋null。无论如何,请一定记住引用类型变量存储的是一个引用——如果两个引用指向同一个对象,

那么改变对象的内容,通过这两个引用获取到的对象也会发生改变。例如:

void Foo (StringBuilder x)
{
x.Append (" world");
} ... StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (y);
Console.WriteLine (y); //输出 hello world

在调用Foo(y)之后,y指向的值变为“hello world”,在Foo方法内部对引用变量x调用Append拼接“ world”字符实现了效果。

这里我们再来看下值类型参数传递如何。正如之前提到的,值类型的值就是它本身。

使用之前的结构类型IntHolder,我们写一些与之前类似的代码来测试下:

void Foo (IntHolder x)
{
x.i=;
} ... IntHolder y = new IntHolder();
y.i=;
Foo (y);
Console.WriteLine (y.i); //输出5

当Foo被调用时,x是个struct类型,并且它的i为5。之后将10赋给了i。

Foo一点都不知道y。当这个方法执行结束,y还是和它之前一模一样。

我们之前展示了一些关于引用类型作为值类型参数传递的例子。

那么你应该明白当IntHolder声明为类时会发生什么。你应该清楚为什么y.i会因此变成10。

引用类型参数

引用类型参数使用的时候不传递其实际值,只是使用变量本身。也就是说不会创建一个新的拷贝,而是使用相同的存储地址。

因此在方法成员中的值类型与引用类型一直都是一样的。

使用引用类型参数在声明和调用的时候需要使用ref关键字——这意味着你要使用引用类型参数将会看起来清晰明确。

我们再来改动下之前的例子测试下:

void Foo (ref StringBuilder x)
{
x = null;
} ... StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (ref y);
Console.WriteLine (y==null); //输出True

这里,因为将y的引用传递给x而不是它的值,所以对x进行的操作相当于对y进行操作一样。在上例中y最后为null。

请将这个结果与上面的没有使用ref关键字的例子结果进行比较。

现在,让我们测试下之前使用结构作为参数的例子加上ref关键字后会发生什么:

void Foo (ref IntHolder x)
{
x.i=;
} ... IntHolder y = new IntHolder();
y.i=;
Foo (ref y);
Console.WriteLine (y.i); //输出10

这两个变量共用一个内存地址,因此改变x时y也会发生改变。所以y.i是10。

注意:通过默认的值类型参数方式传递引用类型变量与通过引用类型参数方式传递值类型变量

有什么区别呢?

你可能已经注意到了在最后一个例子中,将一个结构类型通过引用方式传递,与通过值类型方式传递一个类有同样的结果。

但这不意味他们是一样的。好吧,还是让我们通过下面的代码加深下理解:

void Foo (??? IntHolder x)
{
x = new IntHolder();
} ... IntHolder y = new IntHolder();
y.i=;
Foo (??? y);

假设IntHolder是个结构类型(即值类型),方法参数为引用类型(即将???替换为ref),

执行代码,y最后将会指向一个new IntHolder,y.i会因此变为0。

假设IntHolder是个类(即引用类型),方法参数为值类型(即将???去掉),

执行代码,y的指向不会改变,y.i仍为5。

在调用函数前y与x确实指向同一个对象。理解C#参数传递中的这个区别绝对是相当关键的。

这也是为什么原作者认为当人们谈及对象默认引用传递的时候会非常困惑,其实正确的说法应该是引用类型默认值传递。

Out类型参数

Out类型参数和引用类型参数很像,它不会创建新的内存地址,而是共享内存地址。

使用Out类型参数的方法在声明和调用的时候需要加上关键字Out。这样也会使代码看起来清晰。

虽然Out类型参数与引用类型参数非常相似,但它们还是有区别的。Out类型与引用类型的不同之处:

1.方法调用时传递的变量不用事先赋值。如果方法调用正常结束,即可认为该变量后来被赋值了(这样,你就可以直接读取它的值了)。

2.该参数传递时被看作没有初始化(也就是说你在读取它之前,必须先给它赋值)。

3.在方法结束前必须对这个参数进行赋值,否则编译器会报错。

下面展示一个例子进行加深大家理解,使用的是一个int型作为参数

int是值类型,如果你已经理解了引用类型,相信你也会知道引用类型作为参数会发生什么:

void Foo (out int x)
{
//这里不能读取x,它被认为是没有初始化的,读取会报错 //赋值-在方法结束前必须对其进行赋值,否则报错
x = ; // 这里x可以读取了:
int a = x;
} ... // 声明一个没有初始化的变量
int y; // 虽然y没有初始化,但可以作为out类型参数传递
Foo (out y); // 现在y已经有值了,输出:
Console.WriteLine (y); //输出10

参数数组(params)

参数数组允许传递给一个方法一组数据。定义包含参数数组的方法时必须加上关键字params,调用该方法的时候却不必加上这个关键字。

参数数组必须放在方法参数的最后,并且只能是一维数组。

当使用这类方法时,调用中只要参数与定义时参数数组类型兼容,就能够传递。

由于参数数组的这种使用方式,所以当你想传递一个单独的数组,它的效果就像是值类型参数传递。例如:

void ShowNumbers (params int[] numbers)
{
foreach (int x in numbers)
{
Console.Write (x+" ");
}
Console.WriteLine();
} ... int[] x = {, , };
ShowNumbers (x);
ShowNumbers (, ); //输出:

第一次调用时,x是一个整型数组,效果等同于将它(引用)作为值类型参数传递。

第二次调用时,将会创建一个包含4,5的整型数组,并将它的引用传递(仍然是值类型参数)。

尾奏:总结

翻译出来总是容易使文章读起来拗口难理解。第一次翻译技术文章,我肯定也避免不了。只求能够对大家及我有所帮助。

程序员,英语很重要。

如果您英文比较好的话,我还是建议您读一下原文

如果本文中有疏漏或者理解错误的地方,还请指出,不胜感激。

(译)C#参数传递的更多相关文章

  1. ( 译、持续更新 ) JavaScript 上分小技巧(一)

    感谢好友破狼提供的这篇好文章,也感谢写这些知识点的作者们和将他们整理到一起的作者.这是github上的一篇文章,在这里本兽也就只做翻译,由于本兽英语水平和编程能力都不咋地,如有不好的地方也请多理解体谅 ...

  2. [译] ASP.NET 生命周期 – ASP.NET 上下文对象(五)

    ASP.NET 上下文对象 ASP.NET 提供了一系列对象用来给当前请求,将要返回到客户端的响应,以及 Web 应用本身提供上下文信息.间接的,这些上下文对象也可以用来回去核心 ASP.NET 框架 ...

  3. 【译】ASP.NET MVC 5 教程 - 7:Edit方法和Edit视图详解

    原文:[译]ASP.NET MVC 5 教程 - 7:Edit方法和Edit视图详解 在本节中,我们继续研究生成的Edit方法和视图.但在研究之前,我们先将 release date 弄得好看一点.打 ...

  4. 【译】ASP.NET MVC 5 教程 - 2:添加控制器

    原文:[译]ASP.NET MVC 5 教程 - 2:添加控制器 MVC 表示 模型-视图-控制器.MVC 是一种用于开发应用程序的模式,具备良好架构,可测试和易于维护.基于 MVC 应用程序中包含: ...

  5. Express4.x API (四):Router (译)

    Express4.x API 译文 系列文章 Express4.x API (一):application (译) -- 进行 Express4.x API (二):request (译) -- 完成 ...

  6. [译]PEP 342--增强型生成器:协程

    PEP原文 : https://www.python.org/dev/peps/pep-0342/ PEP标题: Coroutines via Enhanced Generators PEP作者: G ...

  7. REST API设计指导——译自Microsoft REST API Guidelines(四)

    前言 前面我们说了,如果API的设计更规范更合理,在很大程度上能够提高联调的效率,降低沟通成本.那么什么是好的API设计?这里我们不得不提到REST API. 关于REST API的书籍很多,但是完整 ...

  8. [译]The Python Tutorial#4. More Control Flow Tools

    [译]The Python Tutorial#More Control Flow Tools 除了刚才介绍的while语句之外,Python也从其他语言借鉴了其他流程控制语句,并做了相应改变. 4.1 ...

  9. [译]C# 7系列,Part 9: ref structs ref结构

    原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/02/c-7-series-part-9-ref-structs/ 背景 在之前的文章中,我解释了 ...

随机推荐

  1. 替换res\drawable中的图片

    现象 在android开发中,经常会需要替换res\drawable中的图片,打开res\layout下的文件预览布局页面发现图片已经被替换,但在模拟器或者真实机器上运行时发现该图片并没有被替换,还是 ...

  2. canvas绘制文本

    canvas绘制文本 属性和方法 font = value 设置字体 textAlign = value 设置字体对齐方式 start, end, left, right, center textBa ...

  3. python新里程

    为什么要学Python: 2018年6月开始自学Python.因为自己目前做Linux运维,感觉自己还需要掌握一门开发语言,在Java.python.php之间毫不犹豫的选择了Python,因为Pyt ...

  4. ASPxGridView删除、添加、修改成功后,弹出提示对话框的方法

    分为几步: 1.在aspx文件中添加 function EndCallBack(s, e) {    if (s.cpAlertMsg != "" && s.cpA ...

  5. javascript 对象的扩展性

    javascript 对象 的可扩展性 javascript 对象中的可扩展性指的是:是否可以给对象添加新属性.所有的内置对象和自定义对象显示的都是可扩展的,对于宿主对象,则有javascript 引 ...

  6. U3D+SVN: 两份相同资源放在不同目录下导致META的更改

    U3D+SVN: 两份相同资源放在不同目录下导致META的更改. 实际情形:将地图文件map拷一份放在其它目录,回到UNITY编辑器,载入完成后加到磁盘,看到map文件夹下的所有meta都变红了. r ...

  7. 生产者消费者模式做一个golang的定时器

    在主程序启动的时候开一个goroutine作为消费者,用管道连接生产者和消费者,消费者处于无限循环,从管道中获取channel传过来定时event 注意:channel在消费者创建的时候就连通生产者和 ...

  8. Python_05-文件操作

    目录: 1             文件操作 1.1          快速入门 1.1.1       用Python创建一个新文件 1.1.2       文件内容追加,从0到9的10个随机整数 ...

  9. jQuery绑定事件的四種方式

    这篇文章主要介绍的是jQuery绑定事件的四种方式相关内容,下面我们就与大家一起分享. jQuery绑定事件的四种方式 jQuery提供了多种绑定事件的方式,每种方式各有其特点,明白了它们之间的异同点 ...

  10. 宽字符、多字节、unicode、utf-8、gbk编码转化

    今天遇到一个编码的问题,困惑了我很长时间,所以就简要的的了解了一下常用的编码类型. 我们最常见的是assic编码,它是一种单字节编码,对多容纳256个字符. 我们在编程的时候经常遇到unicode,u ...