`
shirlly
  • 浏览: 1652773 次
  • 性别: Icon_minigender_2
  • 来自: 福州
社区版块
存档分类
最新评论

C# - 函数参数的传递

    博客分类:
  • .NET
阅读更多
近段时间,有几个刚刚开始学习C#语言的爱好者问我:C#中的函数,其参数的传递,按值传递和按引用传递有什么区别。针对这一问题,我简单写了个示例程序,用以讲解,希望我没有把他们绕晕。因为,常听别人说起:“你不说我还明白,你一说,我就糊涂了”。
    好,现在开始吧。
    我们知道,在C#中,类型有值类型(例如int)和引用类型(例如string)之分,传递参数有按值传递和按引用传递之分。这样,简单的组合一下,我们可以得到以下几种传递方式:(1)按值传递值类型。(2)按值传递引用类型。(3)按引用传递值类型。(4)按引用传递引用类型。一般来说,除非使用特定的关键字(ref和out)否则参数是按值传递的。也就是说,会传递一个副本。传递副本的一个好处是,可以避免误操作而影响了原始值。原因是在被调用的函数体内,操作的是副本的值,而不是原始值。当然,传递副本也是有副作用的,最为突出的应该是由于复制而产生的性能损耗,这点在大型的值类型身上尤为突出。那么C#的编译器的默认行为为什么不是使用按引用传递参数呢?呵呵,其实我没仔细深入思考过这个问题。我猜测,是因为安全因素吧,就是怕函数误操作了原始值。这点应该和C#的编译器要求显示使用关键字(ref和out)差不多,都是为了清楚地表达使用的意图,以避免误操作。使用ref等关键字,暗示函数调用者知道,在函数体内,也许存在修改原始值的语句,会改变参数的值(或者叫状态)。
    用个简单的示例演示一下。
    示例代码如下所示:
using System;

namespace DonLiang
{
    class Sample
    {
        值类型测试函数#region 值类型测试函数
        public static void foo(int x)
        {
            x = 10;
        }

        //*
        public static void foo(ref int x)
        {
            x = 10;
        }
        //*/

        /**//*
        public static void foo(out int x)
        {
            x = 10;
        }
        //*/
        #endregion

        辅助引用类型#region 辅助引用类型
        public class Point
        {
            private int m_x;
            private int m_y;

            public Point()
            {
                m_x = 0;
                m_y = 0;
            }

            public Point(int x, int y)
            {
                m_x = x;
                m_y = y;
            }

            public void Change(int x, int y)
            {
                m_x = x;
                m_y = y;
            }

            public override string ToString()
            {
                return string.Format("The Point is ({0},{1})", m_x.ToString(), m_y.ToString());
            }
        }
        #endregion

        引用类型测试函数#region 引用类型测试函数
        public static void foo(Point p)
        {
            p.Change(10, 10);
        }

        public static void foo(ref Point p)
        {
            p.Change(100, 100);
        }

        public static void other(Point p)
        {
            Point tmp = new Point(13, 16);
            p = tmp;
        }

        public static void other(ref Point p)
        {
            Point tmp = new Point(138, 168);
            p = tmp;
        }
        #endregion

        Main#region Main
        static void Main(string[] args)
        {
            int n = 5;

            //call the foo(int x) method and check what happened.
            Console.WriteLine("before call foo(int x) the n = " + n.ToString());
            foo(n);
            Console.WriteLine("after call foo(int x) the n = " + n.ToString());

            Console.WriteLine("--------------------------------------------------------------");

            //call the foo(ref int x) method and check what happened.
            Console.WriteLine("before call foo(ref int x) the n = " + n.ToString());
            foo(ref n);
            //foo(out n);
            Console.WriteLine("after call foo(ref int x) the n = " + n.ToString());

            Console.WriteLine("--------------------------------------------------------------");

            Point p = new Point(5, 5);
            Point q = p;

            //call the foo(Point p) method and check what happened.
            Console.WriteLine("before call foo(Point p) the p = " + p.ToString());
            foo(p);
            Console.WriteLine("after call foo(Point p) the p = " + p.ToString());
            Console.WriteLine("q = " + q.ToString());

            Console.WriteLine("--------------------------------------------------------------");

            //call the foo(ref Point p) method and check what happened.
            Console.WriteLine("before call foo(ref Point p) the n = " + p.ToString());
            foo(ref p);
            Console.WriteLine("after call foo(ref Point p) the n = " + p.ToString());
            Console.WriteLine("q = " + q.ToString());

            Console.WriteLine("--------------------------------------------------------------");

            //call the other(Point p) method and check what happened.
            Console.WriteLine("before call other(Point p) the n = " + p.ToString());
            other(p);
            Console.WriteLine("after call other(Point p) the n = " + p.ToString());
            Console.WriteLine("q = " + q.ToString());

            Console.WriteLine("--------------------------------------------------------------");

            //call the other(ref Point p) method and check what happened.
            Console.WriteLine("before call other(ref Point p) the n = " + p.ToString());
            other(ref p);
            Console.WriteLine("after call other(ref Point p) the n = " + p.ToString());
            Console.WriteLine("q = " + q.ToString());

            Console.ReadLine();
        }
        #endregion
    }
}
 

接下来,简单分析一下这个结果:
    (1)按值传递值类型
    初始值为5,调用函数的时候,弄了个副本给函数折腾,于是,从函数返回后,值还是5。嗯,本来应该弄个堆栈图出来的,可是,图是在太难画,因此,我偷懒,用VS2008的调试监视器看看:
   
    (2)按引用传递值类型
    初始值还是5,这次换了个传递参数的方式——按引用传递,这次可不是副本了,而是原始值(的地址),这就类似大牌的武打演员总不能老是使用替身一样,偶尔还是要亲自上阵的。既然是亲自上阵,那么,值被修改为10就不足为奇了。正如结果图所示,n=10了。
   
    (3)按值传递引用类型 和 按引用传递引用类型
    之所以把这两个放在一起讲,是因为,如结果图所示,两种传递方式,都成功修改了值——这两个函数都分别调用了一个辅助修改的函数Change,去修改内部状态,即m_x,m_y的值,从5到10。呃,竟然都可以成功修改原始值,那么,为什么会存在两种方式呢?它们有什么区别吗?分别用在什么地方?为了说明他们的区别,我特意写了两个名为other的函数,在函数内new一个Point对象,并使从参数传递过来的引用这个新生成的Point对象。值得提醒的是,这个引用其定义在函数体外。其运行如上图我用方框框起来那个。
    可以很清楚地看到,通过值传递方式,可以改变其值,却不能改变其本身所引用的对象;而按引用传递方式可以。

    顺便提一下,代码中,有一段注释掉的代码,使用out关键字的。当你尝试将其两者一起写着,然后,编译,C#编译器是会提示错误的(error CS0663: 'foo' cannot define overloaded methods that differ only on ref and out)。其原因是,C#编译器,对ref和out生成的IL代码,是相同的;而在CLR层面,是没有ref和out的区别的。C#中,ref和out的区别,主要是,谁负责初始化这个参数使之能用——ref形式是函数外初始化,而out是函数内初始化。
转自:http://www.cnblogs.com/DonLiang/archive/2008/02/16/1070717.html
分享到:
评论

相关推荐

    c# 调用C++编写 的DLL函数各种参数传递问题。

    C# 调用 C++ 编写的 DLL 函数各种参数传递问题 在 C# 调用 C++ 编写的 DLL 函数时,参数传递是一个非常重要的部分。这篇文章将详细介绍 C# 调用 C++ 编写的 DLL 函数各种参数传递问题,包括不返回值的参数、带...

    multipartform-data 参数传递

    ### multipart/form-data 参数传递 在Web开发中,`multipart/form-data`是一种用于发送表单数据的编码类型,尤其适用于处理文件上传。此编码方式能够使客户端浏览器将表单中的普通文本字段与文件字段一起发送到...

    C# 获取调用函数 参数名称和值

    在获取参数值时,你需要将实际的参数传递给`Invoke`,这在大多数情况下可能无法实现,因为我们通常没有调用函数的实际上下文。在不修改调用代码的情况下,获取参数值的常规方法在C#中并不直接支持,除非你使用AOP...

    C#动态调用DLL并对目标函数传递参数

    本篇将深入探讨如何使用C#进行动态DLL调用以及如何向目标函数传递参数。 首先,动态链接库(DLL)是一种可重用的代码模块,它包含可以由多个应用程序同时调用的函数或资源。在C#中,我们不能像在C++中那样直接使用`...

    C#-2008程序设计基础案例教程-江南-第四章-函数 c#经典案例.ppt

    函数`setfileattr`用于移除文件的只读属性,它的调用表明了如何传递参数给函数。 4.2 使用函数 4.2.1 案例功能展示了如何在实际代码中调用已定义的函数。在`buttonadd_Click`事件处理程序中,我们看到`setfileattr...

    C# 调用函数时动态获取参数名称和值

    首先,理解C#的基础:在C#中,函数参数在编译时就已经确定,因此在函数调用时直接通过位置或命名传递。然而,要动态获取参数名称和值,我们需要利用反射(Reflection)和表达式树(Expression Trees)这两个强大的...

    C#与MATLAB之间传递参数

    C#与MATLAB之间传递参数 C#和MATLAB是两个不同的编程语言,C#是微软公司开发的面向对象的高级编程语言,而MATLAB是MATLAB公司开发的高性能数值计算语言。两者之间的数据传递是非常重要的,因为在实际应用中,我们...

    C#调用C++动态库,执行回调函数并传递结构体参数

    C++动态库中,回调函数是一种特殊的函数,它的指针可以作为参数传递给其他函数,在适当的时候被调用。在C#中,我们将使用委托来表示这个回调函数。C++的回调函数需要考虑函数指针的调用约定,通常使用`__stdcall`...

    C#控制台程序之间参数传递

    在C#编程中,控制台程序之间的参数传递是一项常见的任务,尤其在系统集成或脚本自动化等场景下。本文将详细讲解如何从一个控制台应用程序(源程序)启动另一个控制台应用程序(目标程序),并传递参数字符串,以及...

    C++ 与C#之间的指针参数传递传参参考

    C++ 与C#之间的指针参数传递,实现无限量数据的传递,轻松无压力,方便在C++里面获取或者从C#传递数据到C++的动态库调用内。

    c# 参数传递,参数调用

    在C#编程语言中,参数传递和参数调用是核心概念,它们关乎着函数和方法如何接收和处理数据。理解这些概念对于编写高效且可维护的代码至关重要。本篇文章将深入探讨C#中的参数传递机制及其应用。 首先,我们要了解...

    2.1.4 C++和C#数据传递 -class类型传递

    在C++中,如果你想要在函数内部修改对象,但又不想影响调用者,可以使用const关键字修饰函数参数,声明函数不会修改对象。而在C#中,可以使用readonly或ref关键字来达到类似的效果, readonly保证对象只在构造函数中...

    VS2010轻松学习C#-从零到深入-天轰穿.NET4趣味编程视频教程_第26讲:深入委托

    这种类型安全的方法引用机制使得我们可以在代码中传递方法作为参数,或者将方法存储在变量中。委托是.NET框架的一部分,是强类型的,支持多播,这意味着一个委托实例可以同时引用多个方法。 在C#中,委托的声明通常...

    C#课件 函数 — 参数

    函数参数在C#中扮演着重要的角色,它们提供了灵活性,使得我们可以根据不同的输入数据执行相同的操作。此外,通过参数,函数可以接收不同类型的数据,只要它们满足函数期望的类型。例如,`MaxValue`函数可以接受任何...

    C#_函数手册大全+c#公共函数

    本资源包含了"C#_函数手册大全+c#公共函数",旨在为开发者提供全面的C#函数参考资料。下面我们将详细探讨这两个文档可能涵盖的关键知识点。 一、C#函数基础 1. 函数定义:C#中的函数通过`void FunctionName(params)...

    C# 的函数编程详解

    【C#函数编程详解】 函数式编程是一种编程范式,它强调使用函数作为核心构建块,将计算视为函数的组合,而不是状态的变化或指令的序列。近年来,由于其简洁、可读性强和易于并行处理的特点,函数式编程在IT行业中...

    C#多态性实例,向基类构造函数传递参数.rar

    3. **参数传递的意义**:向基类构造函数传递参数使得我们可以根据派生类实例化时的具体条件来设置基类的状态。这有助于保持代码的灵活性和可扩展性,因为基类的行为可以根据派生类的特性进行调整。 4. **封装和抽象...

    c# winform间传递参数的方法

    本文将详细介绍几种常用的在WinForms中实现窗口间参数传递的方法,并给出具体示例。 #### 二、使用静态变量实现参数传递 一种常见的方法是通过定义静态变量来进行数据共享。这种方法虽然简单,但在实际应用中可能会...

    C#中的参数传递 .doc

    在C#编程语言中,参数传递是理解函数调用机制的关键部分。根据数据类型的不同,参数传递的方式也会有所差异。本文将深入探讨C#中参数传递的机制,特别是针对值类型和引用类型的处理方式。 #### 值类型与引用类型 ...

    VS2010轻松学习C#-从零到深入-天轰穿.NET4趣味编程视频教程_第25讲:委托与事件实例浅析

    委托允许我们传递方法作为参数,创建回调机制,或者实现类似函数式编程中的高阶函数功能。在.NET中,许多内置事件处理机制都是基于委托实现的。例如,System.EventHandler就是一个常见的委托类型,用于处理各种事件...

Global site tag (gtag.js) - Google Analytics