C#锐利体验之第六讲 方法

方法又称成员函数(Member Function),集中体现了类或对象的行为。方法同样分为静态方法和实例方法。静态方法只可以操作静态域,而实例方法既可以操作实例域,也可以操作静态域--虽然这不被推荐,但在某些特殊的情况下会显得很有用。方法也有如域一样的5种存取修饰符--public,protected,internal,protected internal,private,它们的意义如前所述。

方法参数

方法的参数是个值得特别注意的地方。方法的参数传递有四种类型:传值(by value),传址(by reference),输出参数(by output),数组参数(by array)。传值参数无需额外的修饰符,传址参数需要修饰符ref,输出参数需要修饰符out,数组参数需要修饰符params。传值参数在方法调用过程中如果改变了参数的值,那么传入方法的参数在方法调用完成以后并不因此而改变,而是保留原来传入时的值。传址参数恰恰相反,如果方法调用过程改变了参数的值,那么传入方法的参数在调用完成以后也随之改变。实际上从名称上我们可以清楚地看出两者的含义--传值参数传递的是调用参数的一份拷贝,而传址参数传递的是调用参数的内存地址,该参数在方法内外指向的是同一个存储位置。看下面的例子及其输出:

using System;class Test{static void Swap(ref int x, ref int y) {int temp = x;x = y;y = temp;}static void Swap(int x,int y) {int temp = x;x = y;y = temp;}static void Main() {int i = 1, j = 2;Swap(ref i, ref j);Console.WriteLine("i = {0}, j = {1}", i, j);Swap(i,j);Console.WriteLine("i = {0}, j = {1}", i, j);}}
程序经编译后执行输出:

i = 2, j = 1
i = 2, j = 1
我们可以清楚地看到两个交换函数Swap()由于参数的差别--传值与传址,而得到不同的调用结果。注意传址参数的方法调用无论在声明时还是调用时都要加上ref修饰符。

笼统地说传值不会改变参数的值在有些情况下是错误的,我们看下面一个例子:

using System;class Element{public int Number=10;}class Test{static void Change(Element s){s.Number=100;}static void Main() {Element e=new Element();Console.WriteLine(e.Number);Change(e);Console.WriteLine(e.Number);}}
程序经编译后执行输出:

10
100
我们看到即使传值方式仍然改变了类型为Element类的对象t。但严格意义上讲,我们是改变了对象t的域,而非对象t本身。我们再看下面的例子:

using System;class Element{public int Number=10;}class Test{static void Change(Element s){Element r=new Element();r.Number=100;s=r;}static void Main() {Element e=new Element();Console.WriteLine(e.Number);Change(e);Console.WriteLine(e.Number);}}
程序经编译后执行输出:

10
10
传值方式根本没有改变类型为Element类的对象t!实际上,如果我们能够理解类这一C#中的引用类型(reference type)的特性,我们便能看出上面两个例子差别!在传值过程中,引用类型本身不会改变(t不会改变),但引用类型内含的域却会改变(t.Number改变了)!C#语言的引用类型有:object类型(包括系统内建的class类型和用户自建的class类型--继承自object类型),string类型,interface类型,array类型,delegate类型。它们在传值调用中都有上面两个例子展示的特性。

在传值和传址情况下,C#强制要求参数在传入之前由用户明确初始化,否则编译器报错!但我们如果有一个并不依赖于参数初值的函数,我们只是需要函数返回时得到它的值是该怎么办呢?往往在我们的函数返回值不至一个时我们特别需要这种技巧。答案是用out修饰的输出参数。但需要记住输出参数与通常的函数返回值有一定的区别:函数返回值往往存在堆栈里,在返回时弹出;而输出参数需要用户预先制定存储位置,也就是用户需要提前声明变量--当然也可以初始化。看下面的例子:

using System;class Test{static void ResoluteName(string fullname,out string firstname,out string lastname) {string[] strArray=fullname.Split(new char[]{' '});firstname=strArray[0];lastname=strArray[1];}public static void Main() {string MyName="Cornfield Lee";string MyFirstName,MyLastName;ResoluteName(MyName,out MyFirstName,out MyLastName);Console.WriteLine("My first name: {0}, My last name: {1}", MyFirstName, MyLastName);}}
程序经编译后执行输出:

My first name: Cornfield, My last name: Lee
在函数体内所有输出参数必须被赋值,否则编译器报错!out修饰符同样应该应用在函数声明和调用两个地方,除了充当返回值这一特殊的功能外,out修饰符ref修饰符有很相似的地方:传址。我们可以看出C#完全摈弃了传统C/C++语言赋予程序员莫大的自由度,毕竟C#是用来开发高效的下一代网络平台,安全性--包括系统安全(系统结构的设计)和工程安全(避免程序员经常犯的错误)是它设计时的重要考虑,当然我们看到C#并没有因为安全性而丧失多少语言的性能,这正是C#的卓越之处,“Sharp”之处!

数组参数也是我们经常用到的一个地方--传递大量的数组集合参数。我们先看下面的例子:

using System;class Test{static int Sum(params int[] args){int s=0;foreach(int n in args){s+=n;}return s;}static void Main() {int[] var=new int[]{1,2,3,4,5};Console.WriteLine("The Sum:"+Sum(var));Console.WriteLine("The Sum:"+Sum(10,20,30,40,50));}}
程序经编译后执行输出:

The Sum:15
The Sum:150
可以看出,数组参数可以是数组如:var,也可以是能够隐式转化为数组的参数如:10,20,30,40,50。这为我们的程序提供了很高的扩展性。

同名方法参数的不同会导致方法出现多态现象,这又叫重载(overloading)方法。需要指出的是编译器是在编译时便绑定了方法和方法调用。只能通过参数的不同来重载方法,其他的不同(如返回值)不能为编译器提供有效的重载信息。

方法继承
第一等的面向对象机制为C#的方法引入了virtual,override,sealed,abstract四种修饰符来提供不同的继承需求。类的虚方法是可以在该类的继承自类中改变其实现的方法,当然这种改变仅限于方法体的改变,而非方法头(方法声明)的改变。被子类改变的虚方法必须在方法头加上override来表示。当一个虚方法被调用时,该类的实例--亦即对象的运行时类型(run-time type)来决定哪个方法体被调用。我们看下面的例子:

using System;class Parent{public void F() { Console.WriteLine("Parent.F"); }public virtual void G() { Console.WriteLine("Parent.G"); }}class Child: Parent{new public void F() { Console.WriteLine("Child.F"); }public override void G() { Console.WriteLine("Child.G"); }}class Test{static void Main() {Child b = new Child();Parent a = b;a.F();b.F();a.G();b.G();}}
程序经编译后执行输出:

Parent.F
Child.F
Child.G
Child.G
我们可以看到class Child中F()方法的声明采取了重写(new)的办法来屏蔽class Parent中的非虚方法F()的声明。而G()方法就采用了覆盖(override)的办法来提供方法的多态机制。需要注意的是重写(new)方法和覆盖(override)方法的不同,从本质上讲重写方法是编译时绑定,而覆盖方法是运行时绑定。值得指出的是虚方法不可以是静态方法--也就是说不可以用static和virtual同时修饰一个方法,这由它的运行时类型辨析机制所决定。override必须和virtual配合使用,当然也不能和static同时使用。

那么我们如果在一个类的继承体系中不想再使一个虚方法被覆盖,我们该怎样做呢?答案是sealed override (密封覆盖),我们将sealed和override同时修饰一个虚方法便可以达到这种目的:sealed override public void F()。注意这里一定是sealed和override同时使用,也一定是密封覆盖一个虚方法,或者一个被覆盖(而不是密封覆盖)了的虚方法。密封一个非虚方法是没有意义的,也是错误的。看下面的例子:

//sealed.cs// csc /t:library sealed.csusing System;class Parent{public virtual void F() {Console.WriteLine("Parent.F");}public virtual void G() {Console.WriteLine("Parent.G");}}class Child: Parent{sealed override public void F() {Console.WriteLine("Child.F");} override public void G() {Console.WriteLine("Child.G");} }class Grandson: Child{override public void G() {Console.WriteLine("Grandson.G");} }
抽象(abstract)方法在逻辑上类似于虚方法,只是不能像虚方法那样被调用,而只是一个接口的声明而非实现。抽象方法没有类似于{…}这样的方法实现,也不允许这样做。抽象方法同样不能是静态的。含有抽象方法的类一定是抽象类,也一定要加abstract类修饰符。但抽象类并不一定要含有抽象方法。继承含有抽象方法的抽象类的子类必须覆盖并实现(直接使用override)该方法,或者组合使用abstract override使之继续抽象,或者不提供任何覆盖和实现。后两者的行为是一样的。看下面的例子:

//abstract1.cs// csc /t:library abstract1.csusing System;abstract class Parent{public abstract void F();public abstract void G();}abstract class Child: Parent{public abstract override void F();}abstract class Grandson: Child{public override void F(){Console.WriteLine("Grandson.F");}public override void G(){Console.WriteLine("Grandson.G");}}
抽象方法可以抽象一个继承来的虚方法,我们看下面的例子:

//abstract2.cs// csc /t:library abstract2.csusing System;class Parent{public virtual void Method(){Console.WriteLine("Parent.Method");}}abstract class Child: Parent{public abstract override void Method();}abstract class Grandson: Child{public override void Method(){Console.WriteLine("Grandson.Method");}}
归根结底,我们抓住了运行时绑定和编译时绑定的基本机理,我们便能看透方法呈现出的种种overload,virtual,override,sealed,abstract等形态,我们才能运用好方法这一利器!

外部方法

C#引入了extern修饰符来表示外部方法。外部方法是用C#以外的语言实现的方法如Win32 API函数。如前所是外部方法不能是抽象方法。我们看下面的一个例子:

using System;using System.Runtime.InteropServices;class MyClass{[DllImport("user32.dll")]static extern int MessageBoxA(int hWnd, string msg,string caption, int type);public static void Main() {MessageBoxA(0, "Hello, World!", "This is called from a C# app!", 0);}}
程序经编译后执行输出:

这里我们调用了Win32 API函数int MessageBoxA(int hWnd, string msg,string caption, int type)。

时间: 2024-10-03 16:41:30

C#锐利体验之第六讲 方法的相关文章

C#锐利体验之第七讲 域与属性

域 域(Field)又称成员变量(Member Variable),它表示存储位置,是C#中类不可缺少的一部分.域的类型可以是C#中任何数据类型.但对于除去string类型的其他引用类型由于在初始化时涉及到一些类的构造器的操作,我们这里将不提及,我们把这一部分内容作为"类的嵌套"放在"接口 继承与多态"一讲内来阐述. 域分为实例域和静态域.实例域属于具体的对象,为特定的对象所专有.静态域属于类,为所有对象所共用.C#严格规定实例域只能通过对象来获取,静态域只能通过类

C#锐利体验之第八讲 索引器与操作符重载

索引 索引器 索引器(Indexer)是C#引入的一个新型的类成员,它使得对象可以像数组那样被方便,直观的引用.索引器非常类似于我们前面讲到的属性,但索引器可以有参数列表,且只能作用在实例对象上,而不能在类上直接作用.下面是典型的索引器的设计,我们在这里忽略了具体的实现. class MyClass{    public object this [int index]    {        get        {            // 取数据        }        set  

C#锐利体验(六)

第六讲 方法 方法又称成员函数(Member Function),集中体现了类或对象的行为.方法同样分为静态方法和实例方法.静态方法只可以操作静态域,而实例方法既可以操作实例域,也可以操作静态域--虽然这不被推荐,但在某些特殊的情况下会显得很有用.方法也有如域一样的5种存取修饰符--public,protected,internal,protected internal,private,它们的意义如前所述. 方法参数 方法的参数是个值得特别注意的地方.方法的参数传递有四种类型:传值(by val

C#锐利体验

序言 C#语言是一门简单,现代,优雅,面向对象,类型安全,平台独立的一门新型组件编程语言.其语法风格源自C/C++家族,融合了Visual Basic的高效和C/C++强大,是微软为奠定其下一互联网霸主地位而打造的Microsoft.Net平台的主流语言.其一经推出便以其强大的操作能力,优雅的语法风格,创新的语言特性,第一等的面向组件编程的支持而深受世界各地程序员的好评和喜爱."它就是我多年来梦寐以求的计算机语言!"--很多资深程序员拿到C#都是这样的惊讶.从C#语言的名字(C Sha

【教程】【转载】C#锐利体验(李建忠)—01

教程 C#锐利体验南京邮电学院 李建忠(cornyfield@263.net) C#语言是一门简单,现代,优雅,面向对象,类型安全,平台独立的一门新型组件编程语言.其语法风格源自C/C++家族,融合了Visual Basic的高效和C/C++强大,是微软为奠定其下一互联网霸主地位而打造的Microsoft.Net平台的主流语言.其一经推出便以其强大的操作能力,优雅的语法风格,创新的语言特性,第一等的面向组件编程的支持而深受世界各地程序员的好评和喜爱."它就是我多年来梦寐以求的计算机语言!&quo

【教程】【转载】C#锐利体验(李建忠)—04

教程  C#锐利体验 第四讲 类与对象[/b] 南京邮电学院 李建忠(cornyfield@263.net) 组件编程不是对传统面向对象的抛弃,相反组件编程正是面向对象编程的深化和发展.类作为面向对象的灵魂在C#语言里有着相当广泛深入的应用,很多非常"Sharp"的组件特性甚至都是直接由类包装而成.对类的深度掌握自然是我们"Sharp XP"重要的一环. 类 C#的类是一种对包括数据成员,函数成员和嵌套类型进行封装的数据结构.其中数据成员可以是常量,域.函数成员可以

【教程】【转载】C#锐利体验(李建忠)—02

教程  第二讲 C#语言基础介绍 南京邮电学院 李建忠(cornyfield@263.net) 在体验C#的锐利之前,关乎语言基本知识的掌握是必不可少的一环.由于C#基本语言很多源自C/C++,在这里对那些和C/C++类似的地方仅作简单介绍,我们将体验专注于那些区别于传统C/C++的关键的语言基础知识. 数据类型 C#语言的数据类型主要分为两类:值类型和引用类型.另外一种数据类型"指针"是为unsafe上下文编程专门设定的,其中unsafe上下文指对代码进行unsafe标示以满足利用指

【教程】【转载】C#锐利体验(李建忠)—03

教程  C#锐利体验 第三讲 Microsoft.NET平台基础构造 南京邮电学院 李建忠(cornyfield@263.net)抛开Microsoft.NET平台去谈C#是没有意义的,C#之"Sharp"也正在其后端强大的平台.仅仅拘泥于语法层面是体验不了C#的锐利之处的,C#程序很多诡秘之处必须依靠Microsoft.NET平台才能深度的掌握和运用.简单的讲,Microsoft.NET平台是一个建立在开放互联网络协议和标准之上,采用新的工具和服务来满足人们的计算和通信需求的革命性的

C#锐利体验(四)

第四讲 类与对象 组件编程不是对传统面向对象的抛弃,相反组件编程正是面向对象编程的深化和发展.类作为面向对象的灵魂在C#语言里有着相当广泛深入的应用,很多非常"Sharp"的组件特性甚至都是直接由类包装而成.对类的深度掌握自然是我们"Sharp XP"重要的一环. 类 C#的类是一种对包括数据成员,函数成员和嵌套类型进行封装的数据结构.其中数据成员可以是常量,域.函数成员可以是方法,属性,索引器,事件,操作符,实例构建器,静态构建器,析构器.我们将在"第五