本学习主要参考Andrew Troelsen的C#与.NET4高级程序设计,这小节主要述说以下几个东西:
这一小节是上一小节的补充,主要涉及到一下的知识细节:
1、C#方法的各种细节
2、探讨out、ref和params关键字以及可选参数和命名参数
3、方法重载。
4、C#操作数组类型的细节和了解System.Array类类型中包含的功能。
5、枚举结构和结构类型。
6、值类型和引用类型的区别。
7、探讨可空数据类型以及?和??操作符的作用。
方法和参数修饰符
和Main方法类似,自定义方法可以有或者没有参数,也可以有或者没有返回值。方法可以在类或结构的范围内实现(还可以在接口类型中设置原型)、并且可以被各种关键字(如internal、virtual、public等)修饰以限制其行为。方法的基本格式如下:
class A
{
static int Add(int x,int y){return x+y;}
}
接下来总结一下C#的参数修饰符。
1.默认的参数传递行为:
参数传入函数的默认行为是按值传递。简单来说,如果没有为参数标记参数相关的修饰符,数据的副本就会被传入函数。数值数据属于值类型,因此,如果在成员的作用域内改变参数的值,改变的就是调用者数据值的副本,调用者完全不会意识到这种改变。
2.out修饰符:
简单来说,就是输出参数。定义为带有输出参数的方法有义务在退出这个方法前,必须给参数赋一个恰当的值。如,下述方法返回x/y的和到ans中:
static void Add(int x, int y, out int ans)
{
ans=x+y;
}
out参数有个很有用的用途,调用者可以通过它使用一次方法返回多个返回值。并且,调用一个带有输出参数的方法也需要使用out修饰符。
3.ref修饰符:
说一下我的理解,ref关键字的用法,相当于是使得值类型拥有类似引用类型的功能,通常是改变他们的值。下面是引用参数和输入参数的区别:
输出参数不需要在他们被传递给方法之前初始化,因为方法在退出之前必须为输出参数赋值。而引用参数必须在他们被传递给方法之前初始化,因为实在传递一个对已存在变量的引用。
4.params修饰符:
C#使用params关键字支持参数数组的使用。params关键字可以把可变数量的参数(相同类型)作为单个逻辑参数传给方法。例如如下方法:
static double calculate(params double[] values)
{}
这个方法定义为一个带有double类型参数数组,可以给它传递任意数量的double类型参数。例如:calculate(4.1,4.2,...)
说明:为避免歧义,C#要求方法只支持一个params参数,而且必须是参数列表的最后一个参数。
5.定义可选参数:
大体上就是说,我们可以创建一个包含赋值了默认值的参数。例如下面方法:
static void A(string a, string b="SB")
{}
其中第二个参数就是我们赋值了一个默认值的可选参数。
同样,为了避免歧义,可选参数必须放在方法签名的最后,将可选参数放到非可选参数前面会引发编译错误。
6.使用命名参数调用方法:
同可选参数一样,支持命名参数的主要原因也是为了简化与COM的互操作。
命名参数允许我们在调用方法时以任意顺序指定参数的值,因此我们可以使用冒号操作符通过名称来指定参数而不必按位置传递参数。参数用法如下:例如定义一个传递ConsoleColor参数的B方法。void B(ConsoleColor aaa);那么就可以这样用这个方法:B(aaa:ConsoleColor.White);
如果定义了一个包含可选参数的方法,命名参数就非常有用,调用该方法的时候,我们直接给命名参数赋值就行了,不用再指定可选参数的值了。
7.成员重载:
和其他的面向对象语言一样,C#允许方法重载。简而言之,当我们定义了一组名字相同的成员是,如果他们的参数数量(或类型)不同,这样的成员就叫做被重载的成员。
例如同样名字的B方法,传递的参数一个int类型,一个是double类型,这也是允许的。B(int aa)和B(double aa)是两个不同的方法。通过参数的类型不同来区分。
C#中的数组操作
数组是一组通过数字索引来访问的数据项。更准确的说,数组是一组相同类型的数据点(int数组,string数组等)。数组定义类似下面:
int[] myIntArray;
用法如:int[] myIntArray=new int[3];
赋值如:int[0]=199;
1.C#数组初始化语法:
除了逐字填充数组外,还可以用数组初始化语法来填充数组,通过在花括号{}内指定每一个数组项来实现,用法如下:
string[] myStrArray = new string[] { "A", "B" };
2.隐式类型本地数组:
var同样可以用来定义隐式类型本地数组,定义方法如下(注意,数组的元素类型必须一样,所以即使是隐式类型数组,内里元素的类型也要一致,因为编译的时候会确定类型):
var myStrArray = new[] { "A", "B" };
3.定义object数组:
因为System.Object是.net类型系统中所有类型的最终基类,这样的话,如果我们定义了一个object数组,它的子项就可以是任何东西。用法如下:
object[] obj = new object[] { 1, "A", ‘a‘ };
4.使用多维数组:
多维数组主要有两种,一种叫做矩形数组。一种叫做交错数组。
矩形数组排列是以矩阵的形式,声明如下:int[,]=new int[6,6];表示声明了一个6*6的矩形数组。
交错数组,也就是数组的数组。声明如下:int[][] = new int[5][];声明了一个具有五个不同数组的数组。
5.数组作为参数(和返回值)
只要我们创建了一个数组,就完全可以把它当做参数进行传递或者作为成员返回值接受。如下:
static string[] A()
{
string[] B = new string[]{"aaa"};
return B;
}
6.System.Array基类:
我们创建的每一个数组都从System.Array类获得很多功能。使用这些公共成员,我们就能使用统一的对项模型来操作数组。(Clear(),Sort()等)例如,反转一个数组,操作如下:
string[] A = new string[]{"A","B"};
反转用法:Array.Reverse(A);
枚举类型
在构建系统的时候,创建一组符号名来对应已知的数字值会很方便。枚举可以帮助我们实现这个功能。默认情况下,枚举值的存储类型是System.Int32。定义如下:
enum E
{
sss=12,//表示从12开始
aaa,//真实值13
bb//真实值是14
}
1.控制枚举的底层存储:
枚举值的存储类型也可以进行改变,如果我们将枚举值存储类型设置为byte而不是int,那么可以这么写:
enum E:byte
{
aaa=10,
B=1,
CC=100
}
注意,如果我们枚举值超出了枚举类型的范围,那么就会导致编译错误。如上,如果让枚举值等于999就会引发编译错误。
2.声明枚举变量:
因为枚举只不过是用户自定义的类型,我们可以把它们作为函数的返回值、方法参数、本地变量等。
3.System.Enum类型:
.net枚举从该类获得了很多功能,这个类定义了许多用来查询和转换某个枚举的方法。例如Enum.GetUnderlyingType()方法,它用来返回用于保存枚举类型值的数据类型。
4.动态获取枚举的名称/值对:
所有的枚举都支持ToString方法,它返回当前枚举值的字符串名。而另一个方法GetValue则可以返回枚举的值。
结构类型
同C语言中的结构一样,结构不只是一组名称值对,结构式可以包含许多数据字段和操作这些字段的成员的类型。
定义结构:
struct A
{
public int x;
public void Disp()
{
Console.WriteLine("x={0}",x);
}
}
创建结构变量:第一种可以直接以结构名定义,如A a;但是这种必须为结构中的每个公共字段赋值,否则就会出错。另一种方法是用new关键字来创建结构变量,它会调用默认的构造函数,不接受任何输入参数。A a = new A();
值类型和引用类型
C#的结构和数组、字符串以及枚举全都派生自System.ValueType。而该来的作用是确保所有的派生类都分配在栈上而不是垃圾回收堆上。创建和销毁分配再栈上的数据都很快,因为它的生命周期是由定义的作用域决定的,而分配在堆上的数据由.net拉基回收器监控,其生命周期的决定因素有很多。
从功能上说,该类的唯一目的是,重写由System.Object定义的虚方法来使用基于值的而不是基于引用的语法。
由于值类型使用基于值的语法,结构的生命周期是可以预测的,当结构变量离开定义域的范围时,它就会立即从内存中移除。
1.值类型、引用类型和赋值操作符:
值类型赋值(例如结构),在栈上会保留两个副本,给其中一个操作改变值,不会影响另一个副本的值。
和栈中的值类型相比,当对引用类型(类类型)应用赋值操作符时,我们就是在内存中重定向引用变量的指向。他们相当于引用了托管堆中的同一个对象,当改变其中一个的值的时候,另一个也会改变。
2.包含引用类型的值类型:
默认情况下,当值类型包含其他引用类型时,赋值将生成一个引用的副本。这样就有两个独立的结构,但每一个都包含指向内存中同一个对象的引用(浅复制)。如果想要执行一直‘深复制’也就是让副本不受自己影响,即将内部引用的状态完全复制到一个新对象中时,需要实现ICloneable接口。
3.按值传递引用类型:
大概就是将类作为参数传递给方法,并且在方法内部进行多次修改类的参数和重新赋值。例如:
class A
{
public int a;
public A(int aa)
{
a = aa;
}
}
方法B如下:
void B(A a)
{
a.a = 100;//表示改变类内部的值,起作用
a=new A(99);//表示给类重新赋值,不起作用
}
因为, 在这里,传递的值其实是复制了调用者对象的引用。由于B方法与调用者指向同一个对象,所以改变对象的状态数据是可能的,但是无法把引用重新赋值给一个新对象。
4.按引用传递引用类型:
参考上面的例子,如果有一个C方法,它是按照引用传递引用类型。如:
void C(ref A a)
{
a.a = 100;//表示改变类中a的值,起作用
a=new A(99);//表示给a实例分配了一个堆上新的对象,起作用
}
按引用传递引用类型时需要记住的黄金规则如下:
1,如果按引用传递引用类型,被调用者可能改变对象的状态数据的值和所引用的对象。
2,如果按值传递引用类型,被调用者可能改变对象的状态数据的值,但不能改变所引用的对象。
C#可空类型
现在我们应该记得所有数值数据类型(包括Boolean数据类型)都是值类型,按照规则,null用来建立一个空的对象引用,所以值类型永远不可以被赋值为null。
为了定义一个可空的变量类型,应在底层数据类型中添加问号(?)作为后缀。注意,这种语法只对值类型是合法的。如果试图创建一个可空的引用类型,包括字符串,都是遇到编译错误。同非空变量一样,局部可空变量必须赋一个初始值。
1.使用可空类型:
涉及到数据库编程时,可空数据类型可能特别有用,因为一个数据表中的列可能有意是空的。定义如下:public int? a=null;
返回可空类型:
public int? geta()
{
return a;
}
2.??操作符:
关于可空类型需要知道的最后一点是,可以使用??操作符。在获得的值实际上是null时,我们可以用这个操作符给一个可空类型赋值。
例如,当上述方法返回值为null的时候,可以给本地变量赋值为100:
int a = geta() ?? 100;
使用??操作符的好处是,它比传统的if/else条件的写法更加紧凑。不过也可以使用如下代码确保如果值为空,则设置默认的100:
int a= geta();
if(!a.HasValue)
a = 100;
Console.WriteLine("a的值是:{0}",a);
小结:
本小结主要总结了可以用来构建自定义方法的C#关键字,默认情况下参数按值传递。然而,如果参数被标记为ref或者out,我们可以按引用进行传递。除此之外,我还总结了关于方法重载,以及有关数组、枚举和结构如何在C#中定义和在.net类库中表示的方法。最后我们俺就了值类型和引用类型细节,以及包括当作为参数传入方法后,他们如何反应,和如何使用?以及??操作符来和可空数据类型进行交互。