C# 的预定义类型(predefined type)或基元类型(primitive type)被视为其他所有类型的集合。
C#基本数值类型包括
整数类型 8种
科学计算的二进制浮点类型 2种
金融计算十进制浮点类型 1种
int
、double
、string
、char
、bool
、decimal
const 变量类型 变量名=值
枚举并非C#特有,与C语言中的枚举一样,C#中的枚举包含枚举值及与值关联的数字。所谓枚举,就是把这种类型数据可取的类一一列举出来。枚举类型是一种特殊的值类型,是一组已命名的数值常量,常用于声明一组命名的常数。枚举的类型必须是8种整形类型之一。
枚举是可由开发者声明的值类型,枚举的关键特征是在编译时声明了一组可通过名称来引用的常量值,使得代码更易懂。
声明枚举语法
[public] enum 枚举名
{
值1,
值2,
值3...
}
- public 访问修饰符,默认为public公开的,表示无论什么位置都可以访问到。
- enum 关键字,声明枚举的关键字。
- 枚举名,需要符合Pascal命名规范。
枚举常声明在命名空间的下面类的外面,表示在这个命名空间下,所有的类都可以访问与使用它。
public enum UserState
{
Online,
Offline,
Leave,
Busy,
CallMe
}
使用枚举替代布尔能改善代码的可读性,枚举类型默认可通过int
类型相互转换,枚举类型跟int
类型是兼容的。
enum ConnectionState
{
Disconnected,
Connecting,
Connected,
Disconnecting
}
使用枚举值需要为其附加枚举名称前缀
ConnectionState.Connected
根据约定,除了位标志之外的枚举名应该使用单数形式。
ConnectionState
枚举值实际是作为整数常量实现的,默认第一个枚举值为0,后续每一项都递增1,然而,可以显示地为枚举赋值。
enum ConnectionState:short
{
Disconnected,
Connecting = 10,
Connected,
Joined = Connected,
Disconnecting
}
枚举总是具有一个基础类型,可以是除了char
之外的任意整型。事实上,枚举类型的性能完全取决于基础类型的性能。默认基础类型为int
,但可以使用继承语法指定其他类型 。
枚举使用规范
枚举和其他类型稍有不同,因为枚举的继承链是从System.ValueType
到System.Enum
,再到枚举。
枚举之间的类型兼容性
C#不支持不同枚举数组之间的直接转型,但CLR允许,前提是两个枚举具有相同的基础类型。为了避开C#的限制,一个可用的技巧是先转型为System.Array
。
可见枚举类型的变量跟int
和string
类型相互转换,枚举类型默认和int
类型相互兼容,可通过强制类型转换的语法相互转换。但转换一个枚举中没有值的时候不会抛出异常,而是直接将数字显示。枚举和string
类型那个相互转换时,若将枚举转换成string
类型,则直接调用ToString()
,若要将字符串转换为枚举,可采用:UserState state = (UserState)Enum.Parse(typeof(UserState), input)
enum UserState
{
Online,
Offline,
Leave,
Busy,
CallMe
}
Console.Write("Input State Number: ");
string input = Console.ReadLine();
//将字符串转换成枚举类型,调用Parse()方法的目的是为了让其帮助我们将一个字符串转换成对应的枚举类型。
UserState state = (UserState)Enum.Parse(typeof(UserState), input);
Console.WriteLine(state); //0 Online
案例:输入选项输出选项值
enum Color
{
Red,
Green,
Blue
}
while (true) {
Console.WriteLine("Input Option: [0]Red [1]Green [2]Blue");
Console.Write("Option: ");
string input = Console.ReadLine();
switch (input)
{
case "0":
case "1":
case "2":
Color state = (Color)Enum.Parse(typeof(Color), input);
Console.WriteLine($"State: {state}");
break;
default:
Process.GetCurrentProcess().Kill();
break;
}
}
结构可包含构造器、常量、字段、方法、属性、索引器、运算符、事件、嵌套类型等。若同时需要上述多个成员,则应该使用类。与类不同的是,结构是值类型,无需在堆(heap)中分配空间。而且结构不是引用类型,所以也不支持继承。
结构可帮助一次性声明多个不同类型的变量
[public] struct 结构名
{
成员;
}
尽管可利用结构来简化类的声明和实现步骤,但并不是所有情况下都比类优越。作为值类型的结构在传递数据时,实际上是在传递数据本身的副本,而应用类型的类则传递的是引用地址。所以,但传递结构时会比传递类要慢,数据量越大差距就越明显。
public class Dimensions
{
public double Length{get;set;|
public double Width{get;set;}
}
public struct Dimensions
{
public double Length{get;set;}
public double Width{get;set;}
}
创建自定义值类型使用的是和定义类和接口相似的语法,区别在于值类型使用关键字struct
。虽然语言本身未作要求,但对于使用值类型的的一种良好的规范是确保值类型是不可变的。换言之,一旦实例化值类型,实例就不能修改。若要需改则应创建新实例。这是因为:首先,值类型表示的是值。其次,因为值类型是按值复制的,而非按引用复制,所以很容易地迷惑或错误地认为,一个值类型变量的变化也会被观察到引起了另一个变量的变化。
struct Point
{
public int x;
public int y;
}
结构是值类型
null
虽然结构是值类型,但在语法上常常可将其视为类来处理。
var point = new Dimensions();
point.Length = 3;
point.Width = 4;
因为结构是值类型,所以new
运算符和类和其他引用的工作方式不同。new
运算符并不分配堆中的内存,只是调用相应的构造函数,根据传递给它的参数,初始化所有的字段。
Dimensions point;
point.Length = 3;
point.Width = 4;
结构的初始化
public struct Dimensions
{
public double Length{get;set;}
public double Width{get;set;}
public Dimensions(double length, double width)
{
Length = length;
Width = width;
}
public double Diagonal => Math.Sqrt(Length * Length + Width * Width);
}
除了属性和字段,结构可包含方法和构造器,但不可以包含用户定义的默认无参构造器。每个结构自动获得一个无参构造器,将所有字段初始化为各自的默认值。被自动初始化了的结构变量会自动初始化变量。
由于编译器会将声明时的实例字段赋值放到类型的默认构造器中,所以C#禁止在声明时对结构的字段进行赋值。但这并不是表示无需对字段进行初始化。除非所有字段都初始化,否则结构类型的局部变量无法使用。
幸好C#支持有参构造器,而且它们提出了一个有趣的初始化要求。为了确保值类型的局部变量被完全初始化,每个构造器都必须初始化结构中的所有字段。
结构可以有实例构造函数和静态构造函数,但不允许有析构函数。
规范:要确保结构的默认值的有效,总是可以获得结果默认的全零的值。
对结构赋值
将一个结构赋值给另一个结构,也就是将一个结构的值赋值给另一个结构,这和复制类变量不同,复制类变量时只复制引用。
和类不同的是,结构不支持终结器。结构以值的形式复制,不像引用类型那样具有“引用同一性”,所以难以知道在什么时候能安全执行终结器并释放结构占用的非托管资源。垃圾回收器知道在什么时候不再有对一个引用类型实例的“活动”引用,可在此之后的任何时间为这个引用类型运行终结器。但是,“运行时”没有任何机制能够跟踪值类型在特定时刻有多少个副本。
获取类型默认值default
所有值类型都有自动定义的无参构造器将值类型的实例初始化成默认状态。所以,任何值类型都可合法地使用new S()
的语法。除此之外,可使用default
操作符生成结构的默认值。
值类型的继承和接口
所有的值类型都隐式封闭,除此之外,除了枚举之外的所有值类型都派生自System.ValueType
,这意味着结构的继承链总是从object
到System.ValueType
到结构。
值类型也能实现接口,.NET Framework内建的许多值类型都实现了IComparable
和IFormattable
等接口。
System.ValueType
规定了值类型的行为,但没有包含任何附加成员。System.ValueType
重写了object
的所有虚成员。在结构中重写基类方法的规则与类基本一样。区别在于,对于值类型GetHashCode()
的默认实现是将调用转发给结构中的第一个非空字段。此外,Equals()
大量利用了反射。所以,加入一个值类型在集合中频繁使用,尤其是使用了散列码的字典类型的集合,那么值类型应该同时包含对Equals()
和GetHashCode()
的重写,以确保获得较好的性能。