C#1.0

C#1.0

Visual Studio .NET 2002 一起发布的 C# 版本 1.0 非常像 Java。 在 ECMA 制定的设计目标中,它旨在成为一种“简单、现代、面向对象的常规用途语言”。 当时,它和 Java 类似,说明已经实现了上述早期设计目标。

C# 1.0 的主要功能包括:

  • 结构
  • 接口
  • 属性
  • 委托
  • 事件
  • 表达式
  • 语句
  • 特性

基元类型

编译器直接支持的数据类型称为基元类型(primitive type).
基元类型直接映射到到Framework类库(FCL)中存在的类型。例如C#的int直接映射到System.Int32类型。

C#类型关键字 FCL类型 符合公共语言规范(CLS) 说明
sbyte System.SByte 有符号8位值
byte System.Byte 无符号8位值
short System.Int16 有符号16位值
ushort System.UInt16 无符号16位值
int System.Int32 有符号32位值
uint System.UInt32 无符号32位值
long System.Int64 有符号64位值
unlong System.UInt64 无符号64位值
char System.Char 16位Unicode字符
float System.Single IEEE32位浮点值
double System.Double IEEE64位浮点值
bool System.Boolean 真或者假
decimal System.Decimal 128位高精度浮点值,常用于不容许舍入误差的金融计算。
string System.String 字符数组,引用类型
object System.Object 所有类型的基类,引用类型
dynamic(4.0新增) System.Object 对于CLR,dynamic和object完全一致。但C#编译器允许使用简单的语法让dynamic变量参与动态调度。

CLR支持两种类型:引用类型和值类型。

使用class定义的类型一般叫做类,是引用类型。 在运行时,如果声明了引用类型的变量,那变量的值就一直是 null,直到使用 new 运算符显式创建类实例,或直到为这个变量分配可能已在其他位置创建的兼容类型的对象。

类的声明

使用class关键字可以声明类。

class 关键字前面是访问修饰符,从低到高分别是:

  • private(私有的,只能在类的内部访问)
  • protected internal(当前项目中受保护的,只能在同一个程序集下类的内部和继承类中访问)
  • protected(受保护的,只能在内部和继承类中访问)
  • internal(内部的,只能在同一个程序集中访问)
  • public(公共的,访问不受限制)

类名称必须是有效的 C# 标识符名称。 定义的其余部分是类的主体,其中定义了行为和数据。 类上的字段、属性、方法和事件统称为类成员。

类的实例

使用new运算符来创建类的实例,类的实例一般称为对象。

创建对象时,new运算符所做的事情:

  1. 计算类型及其所有基类型(一直到System.Objec)中定义的所有实例字段需要的字节数。托管堆上每个对象都需要一些额外的成语,包括类型对象指针(type object pointer)同步块索引(sync block index)。CLR利用这些成语管理对象。额外成语的字节数要计入对象大小。
  2. 从托管堆中分配类型要求的字节数,从而分配对象的内存,分配的所有字节都设为零。
  3. 初始化对象的类型对象指针同步块索引成员。
  4. 调用类型的实例构造函数。每个类型的构造器都负责初始化该类型定义的实例字段。最终调用到System.Objec的构造函数。

new执行了所有这些操作之后,返回指向新建对象的一个引用(或指针)。

类的继承

在 C# 中仅允许单一继承。 也就是说,一个类仅能从一个基类继承实现。 但是,一个类可实现多个接口。

CLR要求所有类型最终都从System.Object类型派生。

结构

结构类型(“structure type”或“struct type”)是一种可封装数据和相关功能的值类型 。 由于结构是值类型,所以它的实例一般在线程栈上分配。对于托管堆上的类型,在分配内存和 CLR 自动内存管理功能(称为“垃圾回收”)回收内存时都会产生开销,而值类型的实例不受垃圾回收器的控制,因此值类型的使用缓解了托管堆的压力,并减少了应用程序生存期内的垃圾回收次数。

结构的初始化

使用struct 关键字声明结构。

在C#中,必须先初始化已声明的变量,然后才能使用该变量。 由于结构类型变量不能为 null(除非它是可为空的值类型的变量),因此,必须实例化相应类型的实例。 有多种方法可实现此目的。

  • 通常,可使用 new 运算符调用适当的构造函数来实例化结构类型。 每个结构类型都至少有一个构造函数。 这是一个隐式无参数构造函数,用于生成类型的默认值。 还可以使用默认值表达式来生成类型的默认值。
struct MyStruct
{
    public int x;
}

public static void Main()
{
    MyStruct m = new MyStruct();
    Console.WriteLine(m.x);
}
  • 如果结构类型的所有实例字段都是可访问的,则还可以在不使用 new 运算符的情况下对其进行实例化。 在这种情况下,在首次使用实例之前必须初始化所有实例字段。
struct MyStruct
{
    public int x;
}

public static void Main()
{
    MyStruct m;
    m.x = 1;
    Console.WriteLine(m.x);
}

结构的限制

设计结构类型是,具有与类类型相同的功能,但有以下例外:

  • 不能声明无参构造杉树。每个结构类型都已经提供了一个隐式无参构造函数,该构造函数生成类型的默认值。
  • 不能在声明实例字段或属性时对它们进行初始化。 但是,可以在其声明中初始化静态或常量字段或静态属性。
  • 结构类型的构造函数必须初始化该类型的所有实例字段。
  • 结构类型不能从其他类或结构类型继承,也不能作为类的基础类型。 但是,结构类型可以实现接口。
  • 不能在结构类型中声明终结器。

接口

接口包含非抽象类或结构必须实现的一组相关功能的定义。 接口可以定义 static 方法,此类方法必须具有实现。

使用 interface 关键字定义接口,接口名称必须是有效的 C# 标识符名称。 按照约定,接口名称以大写字母 I 开头。

接口的成员有方法属性事件索引器。默认情况下,接口成员是公共的。

interface IInterface
{
    static void StaticMethod()
    {
        Console.WriteLine("hello world!");
    }

    void Mehod();

    string Property { get; }

    event EventHandler<string> MyEvent;

    string this[int i] { get; }
}

属性

属性语法是字段的自然延伸。属性定义包含 getset 访问器的声明,这两个访问器用于检索该属性的值以及对其赋值。

访问属性时,其行为类似于字段。 但与字段不同的是,属性通过访问器实现;访问器用于定义访问属性或为属性赋值时执行的语句。

private int myProperty;
public int MyProperty
{
    get
    {
        return myProperty;
    }
    set
    {
        myProperty = value;
    }
}

// 自动属性
public int MyProperty { get; set; }

委托

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。

.NET Framework通过委托来提供回调函数机制。不同于其他平台的回调机制,委托的功能要多得多。

  • 委托确保回调方法是类型安全的
  • 委托允许顺序调用多个方法
  • 委托支持调用静态方法和实例方法

使用类似于定义方法签名的语法来定义委托类型。 只需向定义添加 delegate 关键字即可。

// 定义一个无返回值的委托类型,它的实例引用一个方法。
// 该方法获取一个string参数,返回void
// 实际上编译器会帮我们生成一个完整的类,编译器定义的类有4个方法:一个构造器、Invoke、BeginInvoke、EndInvoke。
public delegate void HelloDelegate(string name);

// 定义委托之后,可以创建该类型的实例。
// 每个委托对象实际都是一个包装器,其中包装了一个方法和调用该方法时要操作的对象。
HelloDelegate helloDelegate = new HelloDelegate(Console.WriteLine);

// 调用委托
// 实际上等同于 helloDelegate.Invoke("Zhangsan");
helloDelegate("Zhangsan");

// 为了方便C#开发人员,C#编译器自动为委托类型的实例重载了 += 和 -=操作符。
// 这些操作符分别调用 Delegate.Combine 和 Delegate.Remove。可以用这些操作符简化委托链的构造。
HelloDelegate writeDelegate = new HelloDelegate(Console.Write);
// 简写:helloDelegate += writeDelegate;
helloDelegate = (HelloDelegate)Delegate.Combine(helloDelegate, writeDelegate);
// 简写:helloDelegate -= writeDelegate;
helloDelegate = (HelloDelegate)Delegate.Remove(helloDelegate, writeDelegate);

事件

和委托类似,事件是后期绑定机制。 实际上,事件是建立在对委托的语言支持之上的。

事件是对象用于(向系统中的所有相关组件)广播已发生事情的一种方式。 任何其他组件都可以订阅事件,并在事件引发时得到通知。

使用event关键字声明事件。

public event EventHandler<FileListArgs> Progress;

使用委托调用语法调用事件处理程序。

Progress.Invoke(this, new FileListArgs(file));

订阅及取消事件

EventHandler<FileListArgs> onProgress = (sender, eventArgs) =>
    Console.WriteLine(eventArgs.FoundFile);

fileLister.Progress += onProgress;
fileLister.Progress -= onProgress;

表达式

表达式是由一个或多个操作数以及零个或多个运算符组成的序列,其计算结果为一个值、对象、方法或命名空间。

表达式可以包含文本值、方法调用、运算符及其操作数,或简单名称 。 简单名称可以是变量名、类型成员名、方法参数名、命名空间名或类型名。

表达式可以使用运算符(运算符又可使用其他表达式作为参数)或方法调用(方法调用的参数又可以是其他方法调用),因此表达式可以非常简单,也可以极其复杂。

运算符

在表达式中,运算符优先级和结合性决定了操作的执行顺序。 可以使用括号更改由运算符优先级和结合性决定的计算顺序。

当运算符的优先级相同,运算符的结合性决定了运算的执行顺序:

  • 左结合运算符按从左到右的顺序计算。 除赋值运算符null 合并运算符外,所有二元运算符都是左结合运算符。 例如,a + b - c 将计算为 (a + b) - c。
  • 右结合运算符按从右到左的顺序计算。 赋值运算符null 合并运算符条件运算符?:是右结合运算符。 例如,x = y = z 将计算为 x = (y = z)。

下表按最高优先级到最低优先级的顺序列出 C# 运算符(目前最新版本的所有运算符,不特指C#1.0版本中的运算符)。 每行中运算符的优先级相同。

运算符 类别或名称
x.y、f(x)、a[i]、x?.y、x?[y]、x++、x--、x!、new、typeof、checked、unchecked、default、nameof、delegate、sizeof、stackalloc、x->y 基本
+x、-x、!x、~x、++x、--x、^x、(T)x、await、&x、*x、true 和 false 一元运算符
x..y 范围
switch switch表达式
x * y、x / y、x % y 乘除
x + y、x – y 加减
x << y、x >> y 移位
x < y、x > y、x <= y、x >= y、is、as 关系和类型测试
x == y、x != y 相等
x & y 布尔逻辑 AND 或按位逻辑 AND
x ^ y 布尔逻辑 XOR 或按位逻辑 XOR
x | y 布尔逻辑 OR 或按位逻辑 OR
x && y 条件“与”
x || y 条件“或”
x ?? y Null 合并运算符
c ? t : f 条件运算符
x = y、x += y、x -= y、x *= y、x /= y、x %= y、x &= y、x |= y、x ^= y、x <<= y、x >>= y、x ??= y、=> 赋值和 lambda 声明

语句

程序执行的操作采用语句表达。 常见操作包括声明变量、赋值、调用方法、循环访问集合,以及根据给定条件分支到一个或另一个代码块。 语句在程序中的执行顺序称为“控制流”或“执行流”。 根据程序对运行时所收到的输入的响应,在程序每次运行时控制流可能有所不同。

语句可以是以分号结尾的单行代码,也可以是语句块中的一系列单行语句。 语句块括在括号 {} 中,并且可以包含嵌套块。

语句的类型

下表列出了 C# 中的各种语句类型及其关联的关键字和说明

类别 C#关键字/说明
声明语句 声明语句引入新的变量或常量。 变量声明可以选择为变量赋值。 在常量声明中必须赋值。
表达式语句 用于计算值的表达式语句必须在变量中存储该值。
选择语句 选择语句用于根据一个或多个指定条件分支到不同的代码段。例如:ifelseswitchcase
迭代语句 迭代语句用于遍历集合,或重复执行同一组语句直到满足指定的条件。例如:forforeachwhiledo-while
跳转语句 跳转语句将控制转移给另一代码段。例如:breakcontinuereturngotoyield
异常处理 异常处理语句用于从运行时发生的异常情况正常恢复。例如:throwtry-catch-finally
Checked 和 unchecked 用于指定将结果存储在变量中、但该变量过小而不能容纳结果值时,是否允许数值运算导致溢出。
await 语句 如果用 async 修饰符标记方法,则可以使用该方法中的 await 运算符。 在控制到达异步方法的 await 表达式时,控制将返回到调用方,该方法中的进程将挂起,直到等待的任务完成为止。 任务完成后,可以在方法中恢复执行。
yield return 语句 迭代器对集合执行自定义迭代,如列表或数组。 迭代器使用 yield return 语句返回元素,每次返回一个。 到达 yield return 语句时,会记住当前在代码中的位置。 下次调用迭代器时,将从该位置重新开始执行。
fixed 语句 fixed 语句禁止垃圾回收器重定位可移动的变量。
lock 语句 lock 语句用于限制一次仅允许一个线程访问代码块。
带标签的语句 可以为语句指定一个标签,然后使用 goto 关键字跳转到该带标签的语句。
空语句 空语句只含一个分号。 不执行任何操作,可以在需要语句但不需要执行任何操作的地方使用。

特性

Attribute

使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。

特性具有以下属性:

  • 特性向程序添加元数据。 元数据是程序中定义的类型的相关信息。 所有 .NET 程序集都包含一组指定的元数据,用于描述程序集中定义的类型和类型成员。 可以添加自定义特性来指定所需的其他任何信息。
  • 可以将一个或多个特性应用于整个程序集、模块或较小的程序元素(如类和属性)。
  • 特性可以像方法和属性一样接受自变量。
  • 程序可使用反射来检查自己的元数据或其他程序中的元数据。

C#1.0

上一篇:C# TreeHelper帮助类


下一篇:C#递归