代码的重复调用
在C#中有很多方法创建循环。对于你是选择哪种循环依赖你编程的目的和个人的习惯。与其他语言例如C++语言中的循环不同,在C#中有一种foreach的循环,它能简单地反复访问数组或集合对象。
C#引入了一种对于C和C++程序员来说新的循环方法:foreach 循环。不同于创建一个变量,然后简单地去索引一个数组或者其他的数据类型例如集合,foreach 做了一些对你来说相对困难的事。
int[] myArray = {0,1,2,3,4,5,6,7,8,9};
foreach(int a in myArray)
Console.WriteLine("foreach {0}",a);
下面是用For循环做的与上面实现功能相同的例子。
for (int x = 0; x < 10; x++)
Console.WriteLine("{0}",x);
While 循环如下。
int y = 0;
while (y < 10)
{
Console.WriteLine("0}",y);
y++;
}
Do While 循环如下。
int z = 0;
do
{
Console.WriteLine("{0}",z);
z++;
}
while(z < 10);
枚举能使你更容易地去读取自定义枚举对象的内容和让你的程序代码更加可靠。C#能够让你用enum创建一个自己命名的、包含一些常量的集合。这些数据类型可以声明一个集合,这个集合包含名称或者能够定义其他变量的一些值。
例如,如果你定义一组星期的集合,你可能创建一组WeekDay的枚举类型,如下。
enum WeekDay
{
Sunday, Monday, Tuesday,
Wednesday, Thursday, Friday,
Saturday,
}
你能声明一个新的WeekDay类型的变量,并且给它赋值,如下。
WeekDay myday = WeekDays.Monday;
使用这种数据类型能够使你的代码更适合阅读,并且能够阻止对已声明的变量非法和错误地赋值。
下面是更多的枚举类型的特征,它可能对你编写代码更加有用。
如果你要访问枚举变量中的值,可以使用ToString() 属性,如下。
Console.WriteLine(myday.ToString());
默认的,枚举对象内部的第一个成员的值是0,但是你可以为成员赋予不同的值,如下。
enum Colors { Gold = 1, Silver, Bronze};
事实上,你可以为成员赋予特定的值,如下。
enum Colors { Gold = 30, Silver = 20, Bronze = 10};
不像你所熟悉的其他编程语言,C#有两个变化的数据类型:值( value )和引用( reference )。如果这两种类型的性能对你的应用程序很重要或者你对C#如何操纵数据和内存感兴趣的话,那么知道它们之间的区别是很重要的。
当一个变量被声明使用了一个系统已建立的类型或者用户自定义的类型,它就是一个值( value )类型(一种例外是 String 类型,它是引用( reference )类型)。
一个值( value )类型在栈( stack )分配的内存中存储了它的内容。例如。
int x = 50;
这个50的值储存在栈( stack )分配的内存区域。
当变量X在它的生存周期内使用完毕,这个值就出栈并且被抛弃。
使用栈是有效的,但是值( value )在有限的生命周期内是不适合作为不同类的共享数据的。
相反的,引用( reference )类型,例如一个类( class )和数组( array )的实例,储存在堆( heap )分配的内存空间中。在下面的例子中,10个整型变量组成一个数组放在已分配的堆( heap )中。
int[] myInts = new int[10];
当方法结束之后,内存不会返回到堆,当C#的垃圾回收系统认为它不再有用时,会对它进行回收。虽然声明引用类型是一个比较大的系统操作,但是它有具有可以让其他类易于访问的优点。
包装( box )是一个加入到进程中的名称,它能够把一个值( value )转换成引用( reference )类型。当你包装( box )一个变量时,也就创建了一个指向堆( heap )的拷贝引用变量。这个引用( reference )变量是一个object变量,所以这个变量可以使用所有object变量继承的方法。例如,ToString()。如下面代码所示。
int i = 67;
object o = i; // Integer i is boxed
Console.WriteLine(o.ToString()); // This is the same process
当你设计一个为使用object对象的类的时候,你必然要面对解包( unbox )的问题。例如,用一个ArrayList去保存一些整型数,当保存一个整型数时,这个整型数就被包装( box )了。当你要取出这个整型数时,它就必须要解包( unbox )才行。
ArrayList mylist = new ArrayList();
int i = 67;
mylist.Add(i);
int j = (int)mylist[0]; // Must unbox value in arraylist
让我们更加深入一点。当数据作为值( value )类型参数被传进方法( method )中去时,每个参数的副本在栈( stack )中被创建。明显地,如果问题中所涉及的参数是一个大的数据类型,那也就是说,一个用户定义的拥有很多元素的结构体,或者说方法被执行了很多次,这可能对性能有影响。
在这种情况下,更好的方法是用ref关键字来引用这个类型,这与在C++中使用指针来指向一个函数的变量等价。就像在C++中,这种方法有能力改变变量的值,但是这样可能是不安全的。程序员需要在安全和性能上进行比较以决定使用哪种方法更好。
// Passing a parameter by value
int AddTen(int number)
{
return number+10;
}
// Passing a parameter by reference
void AddTen(ref int number)
{
number += 10;
}
out关键字与ref关键字是相似的,但是它要告诉编译器这个函数必须给参数赋值,否则编译器会报错。
// Using the out keyword
void SetToTen(out int number)
{
// If the line below is not present, the code will not compile.
number = 10;
}
结构体 ( struct ) 是类 ( class ) 的一种低消耗的变体。
C#的结构体 ( struct ) 与类 ( class ) 很相似,尽管缺少一些类 ( class ) 的一些特点如继承。当你定义的类型很简单或者定义的数据结构被用在数组中,结构体 ( struct ) 可能比定义类 ( class ) 更加合适。然而,总的来说,你应该更加倾向于类 ( class ) 。
这个例程定义了一个结构体( struct )去储存地理位置。当处于写(WriteLine)的显示状态时,结构体的对象重载(override)了ToString()方法,这样可以产生更有效的输出。如果在结构体 ( struct ) 中没有定义方法的话,那么它不会比类 ( class ) 有更多的优点。
struct myPosition
{
private double longitude;
private double latitude;
public myPosition(double myLong, double myLat)
{
longitude = myLong;
latitude = myLat;
}
public override string ToString()
{
return String.Format("Longitude: {0} degrees, Latitude: {1} degrees", longitude, latitude); ;
}
}
class Program
{
static void Main(string[] args)
{
myPosition Seattle = new myPosition(123, 47);
Console.WriteLine("Position: {0}",Seattle);
Console.ReadLine();
}
}
一段能够处理多种数据类型的代码
通用类是一种可以不区分各种类的类。例如,现在有一个类的功能是操纵整型数,然后又有一个类是操纵浮点指针数,你可以创建一个通用类来做两个类的事。例如,下面的代码实现了一个栈( stack )的通用类。栈( stack )是一个遵循“先进后出”规则的数据结构。用Push方法入栈并且保存数据,用Pop方法出栈并获取数据。
这个栈最多能储存100个任何类型的数据。如果你在实际中应用这种代码,你需要加入检查功能( check )以确保不会有过多的数据进栈和出栈,因为这会导致异常。
请注意这个栈( stack )的定义,在尖括号内的T是数据类型的占位符。注意public和private的使用,在栈( stack )这个类中只有Push和Pop方法是共有函数。保存数据的元素数组( item array )和栈计数器( stack counter )是私有的,这样做,能够为代码的安全性提供保障。
public class Stack< T >
{
// Declare storage space for the stack.
private T[] items = new T[100];
// This variable stores the location of the current object in the stack.
private int stackcounter = 0;
// Define a generic Push method.
public void Push(T data)
{
// To do: check for stackcount out of range.
items[stackcounter++] = data;
}
// Define a generic Pop method.
public T Pop()
{
// To do: check for stackcount out of range.
return items[--stackcounter];
}
}
class Program
{
static void Main(string[] args)
{
// Create an instance of the stack, this one for integers.
Stack<int> myintstack = new Stack<int>();
// Store some values.
for (int i = 1; i < 10; i++)
myintstack.Push(i*10);
// Retreive the values.
for (int i = 1; i< 10; i++)
Console.WriteLine(myintstack.Pop());
// Create an instance of the stack, this one for strings.
Stack<string> mystringstack = new Stack<string>();
// Store some values.
mystringstack.Push("World");
mystringstack.Push("Hello");
// Retreive the values.
Console.WriteLine(mystringstack.Pop());
Console.WriteLine(mystringstack.Pop());
Console.ReadLine();
}
}
在编写应用程序时,每次都要自定义一个栈( stack )类是很乏味的,这就是为什么.NET框架在System.Collections.Generic命名空间中提供了很多的通用集合类,包括。
通用集合类 |
说明 |
List |
可以根据需要增加的链表 |
LinkedList |
每个元素都与下个元素相连的链表 |
Queue |
遵循“先进先出”规则的数据结构 |
Stack |
遵循“先进后出”规则的数据结构 |
在下面这个例子中,我们用通用栈( stack )类保存和取出10个整型数。
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Create the stack.
Stack<int> myStack = new Stack<int>();
// Add ten numbers.
for (int i=0; i<10; i++)
myStack.Push(i);
// Remove and display all the numbers.
while (myStack.Count!=0)
{
Console.WriteLine(myStack.Pop());
}
}
}
}
命名空间(Namespaces)提供给你的程序以组织性
一个命名空间(Namespaces)是组织C#程序中不同类型变量的方法。一个C#程序包含一个或者多个命名空间(Namespaces),而且每个命名空间(Namespaces)既能被程序员定义,也可以是作为被写好的类(class)库的一部分。
例如,System命名空间包含 Console 类(Console 类包含了对控制台窗口程序的读和写的方法)。同样,System命名空间也包含了很多其他的命名空间,例如IO和Collections。.NET 框架单独就拥有超过80个命名空间,每个命名空间又拥有几千个类;命名空间用来减少因为相似命名的类型和方法而导致的混淆。
使用WriteLine方法,它属于System命名空间下的Console类,所以,你可以这样使用它。
System.Console.WriteLine("Hello, World!");
每次写属于某个命名空间的类的方法时,都要在前面加上命名空间和类的引用很麻烦,所以,一个简洁的方法就是在你的C#源文件(source file)开头使用 using 关键字引用命名空间。如下。
using System;
包含了using System 之后,System命名空间就被引用了,之后你就可以这样调用了。
Console.WriteLine("Hello, World!");
当编写一个大的程序时,经常要使用命名空间。使用自定义的命名空间能够提供一种方法去操纵被同样命名的方法和类型。例如,假定你写了一个应用程序,这个应用程序能够同时从磁盘加载统计数据和图片文件。你能够创建两个新的命名空间,一个叫 Image ,另一个叫 StatData 。每个命名空间中的方法的名称都是唯一的,甚至不同的命名空间中有相同的类( class )名。也就是说,你在两个命名空间中都可以有一个叫FileHanding的类,它们都包含了Load方法。你要通过指定Image.FileHanding或者StatData.FileHanding来引用你想使用的类。
下面的例子定义了两个命名空间,每一个都包含了”FileHanding”类。通过指定命名空间可以迅速地区分它们所包含的类( class )和方法( method )。
// Example namespace declarations
namespace Images
{
class FileHandling
{
public void Load() { }
}
}
namespace StatData
{
class FileHandling
{
public void Load() { }
}
}
// The Main method
static void Main(string[] args)
{
StatData.FileHandling mydisk = new StatData.FileHandling();
mydisk.Load();
Images.FileHandling myimage = new Images.FileHandling();
myimage.Load();
}
在你停止程序之前捕获错误
如果在程序运行时因为某些问题而导致程序出错,则异常( Exception )发生。异常( Exception )会停止当前程序的外溢,如果没有做任何处理的话,则程序会简单地停止运行。程序的漏洞( bug )会导致异常的发生。例如,一个数被0除,或者这个数可能成为了不希望的输入结果,或者说用户选择了一个不存在的文件。作为一个程序员,你必须让你的程序能够处理这些问题,不要让你的程序因为这些问题而停止。
C#提供了一些关键字try、catch和finally,这些关键字能够让你的程序侦测到异常,并处理它们,从而让你的程序能够继续运行。它们是让你的程序更加可靠的工具。
try和catch被放在一起使用。try把你认为可能出错的代码圈在一起,catch包含了当出错后程序如何处理的代码。在下面这个例子中,出现了除数为0的计算异常,它在程序中被捕获。如果没有try和catch,这个程序是失败的。
class Program
{
static void Main(string[] args)
{
int x = 0;
try
{
x = 10 / x;
}
catch (System.DivideByZeroException myexception)
{
Console.WriteLine("There was an attempt to divide by zero.");
}
Console.ReadLine();
}
}
指出你捕获异常的类型是一个很好的编程行为。一个try可以有多个catch,每个都处理不同类型的异常。
不管异常发生没有,包含在finally块中的代码总会被执行。使用finally块能够确定代码有返回值。例如,一个文件被关闭。
异常不会总是代表着程序发生了灾难性的问题。往往,它代表的是丢弃了一段不再合适的代码,或者说一种方法( method )没有运行成功。.NET框架很多的类方法都创建了异常机制,它们能够警惕特殊的环境。
你可以用throw关键字来触发异常处理。例如。
class Program
{
static void Main(string[] args)
{
int x = 10;
try
{
if (x > 5) throw new System.ArgumentOutOfRangeException("X is too large");
}
catch (System.ArgumentOutOfRangeException myexception)
{
Console.WriteLine(myexception.Message);
}
Console.ReadLine();
}
}
当你认为程序有可能发生一些不希望发生的状况时,那么就可以使用异常处理。例如,处理一个用户输入,读文件和从Internet获取信息。