在一小时内学会 C#使用例程简单却完整的探索 C# 语言的构造和特点本文特别适合有 C++ 基础却没有太多精力学习 C# 的读者
关于作者
Aisha Ikram
我现在在英国一家软件公司任技术带头人我是计算机科学的硕士我主要使用 NET / C# VBNET ASPNET VC++ MFC ATL COM/DCOM SQL Server /等最近我在学习 NET x 的全部内容我的免费源代码和文章网站是
职业团队带头人
位置英国
简介
C# 是一种具有 C++ 特性Java 样式及 BASIC 快速建模特性的编程语言如果你已经知晓 C++ 语言本文将在不到一小时的时间内带你快速浏览 C# 的语法如果熟悉 Java 语言Java 的编程结构打包和垃圾回收的概念肯定对你快速学习 C# 大有帮助所以我在讨论 C# 语言构造的时候会假设你知道 C++
本文通过一系列例程以简短但全面的方式讨论了 C# 语言构造和特性所以你仅需略览代码片刻即可了解其概念
注意本文不是为 C# 宗师而写有很多初学者的 C# 文章这只是其中之一
接下来关于 C# 的讨论主题
编程结构
命名空间
数据类型
变量
运算符与表达式
枚举
语句
类与结构
修饰符
属性
接口
函数参数
数组
索引器
装箱与拆箱
委托
继承与多态
以下主题不会进行讨论
C++ 与 C# 的共同点
诸如垃圾回收线程文件处理等概念
数据类型转换
异常处理
NET 库
编程结构
和 C++ 一样C# 是大小写敏感的半角分号(;)是语句分隔符和 C++ 有所区别的是C# 中没有单独的声明(头)和实现(CPP)文件所有代码(类声明和实现)都放在扩展名为 cs 的单一文件中
看看 C# 中的 Hello World 程序
复制内容到剪贴板
代码:
using System;
namespace MyNameSpace
{
class HelloWorld
{
static void Main(string[] args)
{
ConsoleWriteLine (Hello World);
}
}
C# 中所有内容都打包在类中
而所有的类又打包在命名空间中(正如文件存与文件夹中)
和 C++ 一样
有一个主函数作为你程序的入口点
C++ 的主函数名为 main
而 C# 中是大写 M 打头的 Main
类块或结构定义之后没有必要再加一个半角分号C++ 中是这样但 C# 不要求
命名空间
每个类都打包于一个命名空间命名空间的概念和 C++ 完全一样但我们在 C# 中比在 C++ 中更加频繁的使用命名空间你可以用点()定界符访问命名空间中的类上面的 Hello World 程序中MyNameSpace 是其命名空间
现在思考当你要从其他命名空间的类中访问 HelloWorld 类
复制内容到剪贴板
代码:
using System;
namespace AnotherNameSpace
{
class AnotherClass
{
public void Func()
{
ConsoleWriteLine (Hello World);
}
}
}
现在在你的 HelloWorld 类中你可以这样访问
复制内容到剪贴板
代码:
using System;
using AnotherNameSpace; // 你可以增加这条语句
namespace MyNameSpace
{
class HelloWorld
{
static void Main(string[] args)
{
AnotherClass obj = new AnotherClass();
objFunc();
}
}
}
在 NET 库中System 是包含其他命名空间的顶层命名空间默认情况下存在一个全局命名空间所以在命名空间外定义的类直接进到此全局命名空间中因而你可以不用定界符访问此类
你同样可以定义嵌套命名空间
Using
#include 指示符被后跟命名空间名的 using 关键字代替了正如上面的 using SystemSystem 是最基层的命名空间所有其他命名空间和类都包含于其中System 命名空间中所有对象的基类是 Object
变量
除了以下差异C# 中的变量几乎和 C++ 中一样
C# 中(不同于 C++)的变量总是需要你在访问它们前先进行初始化否则你将遇到编译时错误故而不可能访问未初始化的变量
你不能在 C# 中访问一个挂起指针
超出数组边界的表达式索引值同样不可访问
C# 中没有全局变量或全局函数取而代之的是通过静态函数和静态变量完成的
数据类型
所有 C# 的类型都是从 object 类继承的有两种数据类型
基本/内建类型
用户定义类型
以下是 C# 内建类型的列表
类型 字节 描述
byte unsigned byte
sbyte signed byte
short signed short
ushort unsigned short
int signed integer
uint unsigned integer
long signed long
ulong unsigned long
float floating point number
double double precision number
decimal fixed precision number
string Unicode string
char Unicode char
bool true false boolean
注意C# 的类型范围和 C++ 不同例如long 在 C++ 中是 字节而在 C# 中是 字节bool 和 string 类型均和 C++ 不同bool 仅接受真假而非任意整数
用户定义类型文件包含
类 (class)
结构(struct)
接口(interface)
以下类型继承时均分配内存
值类型
参考类型
值类型
值类型是在堆栈中分配的数据类型它们包括了
? 除字符串所有基本和内建类型
? 结构
? 枚举类型
引用类型
引用类型在堆(heap)中分配内存且当其不再使用时将自动进行垃圾清理和 C++ 要求用户显示创建 delete 运算符不一样它们使用新运算符创建且没有 delete 运算符在 C# 中它们自动由垃圾回收系统回收
引用类型包括
? 类
? 接口
? 集合类型如数组
? 字符串
枚举
C# 中的枚举和 C++ 完全一样通过关键字 enum 定义
例子
复制内容到剪贴板
代码:
enum Weekdays
{
Saturday Sunday Monday Tuesday Wednesday Thursday Friday
}
类与结构
除了内存分配的不同外类和结构就和 C++ 中的情况一样类的对象在堆中分配并使用 new 关键字创建而结构是在栈(stack)中进行分配C# 中的结构属于轻量级快速数据类型当需要大型数据类型时你应该创建类
例子
复制内容到剪贴板
代码:
struct Date
{
int day;
int month;
int year;
}
class Date
{
int day;
int month;
int year;
string weekday;
string monthName;
public int GetDay()
{
return day;
}
public int GetMonth()
{
return month;
}
public int GetYear()
{
return year;
}
public void SetDay(int Day)
{
day = Day ;
}
public void SetMonth(int Month)
{
month = Month;
}
public void SetYear(int Year)
{
year = Year;
}
public bool IsLeapYear()
{
return (year/ == );
}
public void SetDate (int day int month int year)
{
}
}
属性
如果你熟悉 C++ 面向对象的方法你一定对属性有自己的认识对 C++ 来说前面例子中 Date 类的属性就是 daymonth 和 year而你添加了 Get 和 Set 方法C# 提供了一种更加便捷简单而又直接的属性访问方式
所以上面的类应该写成这样
复制内容到剪贴板
代码:
using System;
class Date
{
public int Day{
get {
return day;
}
set {
day = value;
}
}
int day;
public int Month{
get {
return month;
}
set {
month = value;
}
}
int month;
public int Year{
get {
return year;
}
set {
year = value;
}
}
int year;
public bool IsLeapYear(int year)
{
return year%== ? true: false;
}
public void SetDate (int day int month int year)
{
thisday = day;
thismonth = month;
thisyear = year;
}
}
这里是你 get 和 set 属性的方法
复制内容到剪贴板
代码:
class User
{
public static void Main()
{
Date date = new Date();
dateDay = ;
dateMonth = ;
dateYear = ;
ConsoleWriteLine
(Date: {}/{}/{} dateDay dateMonth dateYear);
}
}
修饰符
你必须知道 C++ 中常用的 publicprivate 和 protected 修饰符我将在这里讨论一些 C# 引入的新的修饰符
readonly
readonly 修饰符仅用于修饰类的数据成员正如其名字说的一旦它们已经进行了写操作直接初始化或在构造函数中对其进行了赋值readonly 数据成员就只能对其进行读取readonly 和 const 数据成员不同之处在于 const 要求你在声明时进行直接初始化看下面的例程
复制内容到剪贴板
代码:
class MyClass
{
const int constInt = ; //直接进行
readonly int myInt = ; //直接进行
readonly int myInt;
public MyClass()
{
myInt = ; //间接进行
}
public Func()
{
myInt = ; //非法
ConsoleWriteLine(myIntToString());
}
}
sealed
带有 sealed 修饰符的类不允许你从它继承任何类所以如果你不想一个类被继承你可以对该类使用 sealed 关键字
复制内容到剪贴板
代码:
sealed class CanNotbeTheParent
{
int a = ;
}
unsafe
你可以使用 unsafe 修饰符在 C# 中定义一个不安全上下文在不安全上下文中你可以插入不安全代码如 C++ 的指针等参见以下代码
复制内容到剪贴板
代码:
public unsafe MyFunction( int * pInt double* pDouble)
{
int* pAnotherInt = new int;
*pAnotherInt = ;
pInt = pAnotherInt;
*pDouble = ;
}
接口
如果你有 COM 的思想你马上就知道我在说什么了接口是只包含函数签名而在子类中实现的抽象基类在 C# 中你可以用 interface 关键字声明这样的接口类NET 就是基于这样的接口的C# 中你不能对类进行多重继承——这在 C++ 中是允许的通过接口多重继承的精髓得以实现即你的子类可以实现多重接口(译注由此可以实现多重继承)
复制内容到剪贴板
代码:
using System;
interface myDrawing
{
int originx
{
get;
set;
}
int originy
{
get;
set;
}
void Draw(object shape);
}
class Shape: myDrawing
{
int OriX;
int OriY;
public int originx
{
get{
return OriX;
}
set{
OriX = value;
}
}
public int originy
{
get{
return OriY;
}
set{
OriY = value;
}
}
public void Draw(object shape)
{
// 做要做的事
}
// 类自身的方法
public void MoveShape(int newX int newY)
{
}
}
数组
数组在 C# 中比 C++ 中要高级很多数组分配于堆中所以是引用类型的你不能访问数组边界外的元素所以 C# 防止你引发那种 bug同时也提供了迭代数组元素的帮助函数foreach 是这样的迭代语句之一C++ 和 C# 数组的语法差异在于
方括号在类型后面而不是在变量名后面
创建元素使用 new 运算符
C# 支持一维多维和交错数组(数组的数组)
例子
复制内容到剪贴板
代码:
int[] array = new int[]; // int 型一维数组
for (int i = ; i < arrayLength; i++)
array = i;
int[] array = new int[]; // int 型二维数组
array[] = ;
int[] array = new int[]; // int 型三维数组
array[] = ;
int[][] arrayOfarray = new int[]; // int 型交错数组 数组的数组
arrayOfarray[] = new int[];
arrayOfarray[] = new int[] {};
索引器
索引器用于书写一个可以通过使用 [] 像数组一样直接访问集合元素的方法你所需要的只是指定待访问实例或元素的索引索引器的语法和类属性语法相同除了接受作为元素索引的输入参数外
例子
注意CollectionBase 是用于建立集合的库类List 是 CollectionBase 中用于存放集合列表的受保护成员
复制内容到剪贴板
代码:
class Shapes: CollectionBase
{
public void add(Shape shp)
{
ListAdd(shp);
}
//indexer
public Shape this[int index]
{
get {
return (Shape) List[index];
}
set {
List[index] = value ;
}
}
}
装箱/拆箱
装箱的思想在 C# 中是创新的正如前面提到的所有的数据类型无论是内建的还是用户定义的都是从 System 命名空间的基类 object 继承的所以基础的或是原始的类型打包为一个对象称为装箱相反的处理称为拆箱
例子
复制内容到剪贴板
代码:
class Test
{
static void Main()
{
int myInt = ;
object obj = myInt ; // 装箱
int myInt = (int) obj; // 拆箱
}
}
例程展示了装箱和拆箱两个过程一个 int 值可以被转换为对象并且能够再次转换回 int当某种值类型的变量需要被转换为一个引用类型时便会产生一个对象箱保存该值拆箱则完全相反当某个对象箱被转换回其原值类型时该值从箱中拷贝至适当的存储空间
函数参数
C# 中的参数有三种类型
按值传递/输入参数
按引用传递/输入输出参数
输出参数
如果你有 COM 接口的思想而且还是参数类型的你会很容易理解 C# 的参数类型
按值传递/输入参数
值参数的概念和 C++ 中一样传递的值复制到了新的地方并传递给函数
例子
复制内容到剪贴板
代码:
SetDay();
void SetDay(int day)
{
}
按引用传递/输入输出参数
C++ 中的引用参数是通过指针或引用运算符 & 传递的C# 中的引用参数更不易出错你可以传递一个引用地址你传递一个输入的值并通过函数得到一个输出的值因此引用参数也被称为输入输出参数
你不能将未初始化的引用参数传递给函数C# 使用关键字 ref 指定引用参数你同时还必须在传递参数给要求引用参数的函数时使用关键字 ref
例子
复制内容到剪贴板
代码:
int a= ;
FunctionA(ref a); // 使用 ref否则将引发编译时错误
ConsoleWriteLine(a); // 打印
复制内容到剪贴板
代码:
void FunctionA(ref int Val)
{
int x= Val;
Val = x* ;
}
输出参数
输出参数是只从函数返回值的参数输入值不要求C# 使用关键字 out 表示输出参数
例子
复制内容到剪贴板
代码:
int Val;
GetNodeValue(Val);
复制内容到剪贴板
代码:
bool GetNodeValue(out int Val)
{
Val = value;
return true;
}
参数和数组的数量变化
C# 中的数组使用关键字 params 进行传递一个数组类型的参数必须总是函数最右边的参数只有一个参数可以是数组类型你可以传送任意数量的元素作为数组类型的参数看了下面的例子你可以更好的理解
注意使用数组是 C# 提供用于可选或可变数量参数的唯一途径
例子
复制内容到剪贴板
代码:
void Func(params int[] array)
{
ConsoleWriteLine(number of elements {} arrayLength);
}
复制内容到剪贴板
代码:
Func(); // 打印
Func(); // 打印
Func(); // 打印
Func(new int[] {}); // 打印
int[] array = new int[] {};
Func(array); // 打印
运算符与表达式
运算符和表达式跟 C++ 中完全一致然而同时也添加了一些新的有用的运算符有些在这里进行了讨论
is 运算符
is 运算符是用于检查操作数类型是否相等或可以转换is 运算符特别适合用于多态的情形is 运算符使用两个操作数其结果是布尔值参考例子
复制内容到剪贴板
代码:
void function(object param)
{
if(param is ClassA)
//做要做的事
else if(param is MyStruct)
//做要做的事
}
}
as 运算符
as 运算符检查操作数的类型是否可转换或是相等(as 是由 is 运算符完成的)如果是则处理结果是已转换或已装箱的对象(如果操作数可以装箱为目标类型参考 装箱/拆箱)如果对象不是可转换的或可装箱的返回值为 null看看下面的例子以更好的理解这个概念
复制内容到剪贴板
代码:
Shape shp = new Shape();
Vehicle veh = shp as Vehicle; // 返回 null类型不可转换
Circle cir = new Circle();
Shape shp = cir;
Circle cir = shp as Circle; //将进行转换
object[] objects = new object[];
objects[] = Aisha;
object[] = new Shape();
string str;
for(int i=; i&< objectsLength; i++)
{
str = objects as string;
if(str == null)
ConsoleWriteLine(can not be converted);
else
ConsoleWriteLine({}str);
}
复制内容到剪贴板
代码:
Output:
Aisha
can not be converted
语句
除了些许附加的新语句和修改外C# 的语句和 C++ 的基本一致
以下是新的语句
foreach
用于迭代数组等集合
例子
复制内容到剪贴板
代码:
foreach (string s in array)
ConsoleWriteLine(s);
lock
在线程中使代码块称为重点部分
(译注lock 关键字将语句块标记为临界区方法是获取给定对象的互斥锁执行语句然后释放该锁lock 确保当一个线程位于代码的临界区时另一个线程不进入临界区如果其他线程试图进入锁定的代码则它将一直等待(即被阻止)直到该对象被释放)
checked/unchecked
用于数字操作中的溢出检查
例子
复制内容到剪贴板
代码:
int x = IntMaxValue; x++; // 溢出检查
{
x++; // 异常
}
unchecked
{
x++; // 溢出
}
下面的语句已修改(译注原文如此疑为作者笔误)
Switch
Switch 语句在 C# 中修改过
现在在执行一条 case 语句后程序流不能跳至下一 case 语句之前在 C++ 中这是可以的
例子
复制内容到剪贴板
代码:
int var = ;
switch (var)
{
case : ConsoleWriteLine(<Value is >); // 这里没有 break
case : ConsoleWriteLine(<Value is >); break;
}
C++ 的输出
复制内容到剪贴板
代码:
<Value is ><Value is >
而在 C# 中你将得到一个编译时错误
复制内容到剪贴板
代码:
error CS: Control cannot fall through
from one case label (case :) to another
然而你可以像在 C++ 中一样这么用
复制内容到剪贴板
代码:
switch (var)
{
case :
case : ConsoleWriteLine( or <VALUE is >); break;
}
你还可以用常数变量作为 case 值
例子
复制内容到剪贴板
代码:
const string WeekEnd = Sunday;
const string WeekDay = Monday;
string WeekDay = ConsoleReadLine();
switch (WeekDay )
{
case WeekEnd: ConsoleWriteLine(Its weekend!!); break;
case WeekDay: ConsoleWriteLine(Its Monday); break;
}
委托
委托让我们可以把函数引用保存在变量中这就像在 C++ 中使用 typedef 保存函数指针一样
委托使用关键字 delegate 声明看看这个例子你就能理解什么是委托
例子
复制内容到剪贴板
代码:
delegate int Operation(int val int val);
public int Add(int val int val)
{
return val + val;
}
public int Subtract (int val int val)
{
return val val;
}
public void Perform()
{
Operation Oper;
ConsoleWriteLine(Enter + or );
string optor = ConsoleReadLine();
ConsoleWriteLine(Enter operands);
string opnd = ConsoleReadLine();
string opnd = ConsoleReadLine();
int val = ConvertToInt (opnd);
int val = ConvertToInt (opnd);
if (optor == +)
Oper = new Operation(Add);
else
Oper = new Operation(Subtract);
ConsoleWriteLine( Result = {} Oper(val val));
}
继承与多态
C# 只允许单一继承多重继承可以通过接口达到
例子
复制内容到剪贴板
代码:
class Parent{
}
class Child : Parent
虚函数
虚函数在 C# 中同样是用于实现多态的概念的除了你要使用 override 关键字在子类中实现虚函数外父类使用同样的 virtual 关键字每个重写虚函数的类都使用 override 关键字(译注作者所说的同样除……外都是针对 C# 和 C++ 而言的)
复制内容到剪贴板
代码:
class Shape
{
public virtual void Draw()
{
ConsoleWriteLine(ShapeDraw) ;
}
}
class Rectangle : Shape
{
public override void Draw()
{
ConsoleWriteLine(RectangleDraw);
}
}
class Square : Rectangle
{
public override void Draw()
{
ConsoleWriteLine(SquareDraw);
}
}
class MainClass
{
static void Main(string[] args)
{
Shape[] shp = new Shape[];
Rectangle rect = new Rectangle();
shp[] = new Shape();
shp[] = rect;
shp[] = new Square();
shp[]Draw();
shp[]Draw();
shp[]Draw();
}
}
Output:
ShapeDraw
RectangleDraw
SquareDraw
使用new隐藏父类函数
你可以隐藏基类中的函数而在子类中定义其新版本关键字 new 用于声明新的版本思考下面的例子该例是上一例子的修改版本注意输出我用 关键字 new 替换了 Rectangle 类中的关键字 override
复制内容到剪贴板
代码:
class Shape
{
public virtual void Draw()
{
ConsoleWriteLine(ShapeDraw) ;
}
}
class Rectangle : Shape
{
public new void Draw()
{
ConsoleWriteLine(RectangleDraw);
}
}
class Square : Rectangle
{
//这里不用 override
public new void Draw()
{
ConsoleWriteLine(SquareDraw);
}
}
class MainClass
{
static void Main(string[] args)
{
ConsoleWriteLine(Using Polymorphism:);
Shape[] shp = new Shape[];
Rectangle rect = new Rectangle();
shp[] = new Shape();
shp[] = rect;
shp[] = new Square();
shp[]Draw();
shp[]Draw();
shp[]Draw();
ConsoleWriteLine(Using without Polymorphism:);
rectDraw();
Square sqr = new Square();
sqrDraw();
}
}
Output:
Using Polymorphism
ShapeDraw
ShapeDraw
ShapeDraw
Using without Polymorphism:
RectangleDraw
SquareDraw
多态性认为 Rectangle 类的 Draw 方法是和 Shape 类的 Draw 方法不同的另一个方法而不是认为是其多态实现所以为了防止父类和子类间的命名沖突我们只有使用 new 修饰符
注意你不能在一个类中使用一个方法的两个版本一个用 new 修饰符另一个用 override 或 virtual就像在上面的例子中我不能在 Rectangle 类中增加另一个名为 Draw 的方法因为它是一个 virtual 或 override 的方法同样在 Square 类中我也不能重写 Shape 类的虚方法 Draw
调用基类成员
如果子类的数据成员和基类中的有同样的名字为了避免命名沖突基类成员和函数使用 base 关键字进行访问看看下面的例子基类构造函数是如何调用的而数据成员又是如何使用的
复制内容到剪贴板
代码:
public Child(int val) :base(val)
{
myVar = ;
basemyVar;
}
OR
public Child(int val)
{
base(val);
myVar = ;
basemyVar;
}
前景展望
本文仅仅是作为 C# 语言的一个快速浏览以便你可以熟悉该语言的一些特性尽管我尝试用实例以一种简短而全面的方式讨论了 C# 几乎所有的主要概念但我认为还是有很多内容需要增加和讨论的
以后我会增加更多的没有讨论过的命令和概念包括事件等我还想给初学者写一下怎么用 C# 进行 Windows 编程