📝 预定义数据类型

C# 是面向对象的强类型高级语言,内置了用于存储不同类型数据的内置数据类型。每种数据类型都包含特定的取值范围,使用这些数据类型来表示在应用程序中存储的数据。数据类型进一步又被分类为值类型引用类型,这取决于特定类型的变量是存储其自己的数据还是指向存储器中的数据的引用。

Type Alias Actual Type Type Size (bit) Range (values)
byte Byte Unsigned integer 8 0 to 255
sbyte SByte Signed integer 8 -128 to 127
int Int32 Signed integer 32 -2,147,483,648 to 2,147,483,647
uint UInt32 Unsigned integer 32 0 to 4294967295
short Int16 Signed integer 16 -32,768 to 32,767
ushort UInt16 Unsigned integer 16 0 to 65,535
long Int64 Signed integer 64 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
ulong UInt64 Unsigned integer 64 0 to 18,446,744,073,709,551,615
float Single Single-precision floating point type 32 -3.402823e38 to 3.402823e38
double Double Double-precision floating point type 64 -1.79769313486232e308 to 1.79769313486232e308
char Char A single Unicode character 16 Unicode symbols used in text
bool Boolean Logical Boolean type 8 True or False
object Object Base type of all other types
string String A sequence of characters
decimal Decimal Precise fractional or integral type that can represent decimal numbers with 29 significant digits 128 (+ or -)1.0 x 10e-28 to 7.9 x 10e28
DateTime DateTime Represents date and time 0:00:00am 1/1/01 to 11:59:59pm 12/31/9999
Byte System.Byte Struct
sbyte System.SByte struct
int System.Int32 struct
uint System.UInt32 struct
short System.Int16 struct
ushort System.UInt16 struct
long System.Int64 struct
ulong System.UInt64 struct
float System.Single struct
double System.Double struct
char System.Char struct
bool System.Boolean struct
object System.Object Class
Alias Type Name .Net Type
string System.String Class
decimal System.Decimal struct
DateTime System.DateTime struct

值类型

值类型特点是:变量直接存储其值

bool / byte / char/ decimal / double / enum / float / int/ long / sbyte / short / struct / uint / ulong / ushort

引用类型

引用类型特点是:引用类型不直接存储其值,它存储值的引用内存地址。

string / all array(所有数组,即使它们的元素是值类型) / Class / Delegates…

变量

  1. 语法
  2. <datatype><variablename>=<value>;
  3. 示例
  4. string name = "wang";
  5. 同时声明多个
  6. string name1,name2 = "wang";

编译器需要用某个初始值对变量进行初始化,之后才能在操作中引用该变量。
需要注意

  • 变量是类或结构中的字段,如果没有显式初始化,创建这些变量时,其默认值就是类型默认值
  • 方法的局部变量必须在代码中显式初始化才能在语句中使用
  • 在C#中实例化一个引用对象需要使用new关键字把该引用指向存储在堆上的一个对象

    类型推断

    使用 var 类型预先不用知道变量的类型,编译器可以根据变量的初始化值“推断”变量类型。
    需要注意

  • 变量必须初始化,否则编译器就没有推断变量类型的依据

  • 初始化器不能为空
  • 初始化器必须放在表达式中
  • 不能把初始化器设置为一个对象,除非在初始化器中创建了一个新对象
    1. var name = "wang";

    变量作用域

    变量作用域是可以访问该变量的代码区域。

需要注意

  • 只要类在某个作用域内,其字段(也称为成员变量)也在该作用域内
  • 局部变量存在于表示声明该变量的块语句或方法结束的右花括号之前的作用域内
  • forwhile 或类似语句中声明的局部变量存在于该循环体内

    常量

    常量是其值在使用过程中不会发生变化的变量。在声明和初始化变量时,在变量的前面加上关键字 const,就可以把该变量指定为一个常量。

    1. const string conntionName = "testConntion";

    常量具有如下特点

  • 常量必须在声明时初始化。指定其值后就不能再改写

  • 常量的值必须能在编译时用于计算。因此不能用从一个变量中提取的值来初始化常量。如果需要这么做,应使用只读字段 readonly
  • 常量总是静态的,但注意:不必(实际上,是不允许)在常量声明中包含修饰符

使用常量的好处

  • 常量使程序变得更易于阅读(使用易于读取理解的名称替代了较难读取的数字或字符串)
  • 常量使程序更易于修改
  • 常量更容易避免程序出现错误,如果在声明常量的位置以外将另一个值赋给常量,编译器就会报错

    只读字段

    常量的概念是一个包含不能修改的值的变量。但有时可能需要一些变量,其值不应改变,但在运行前其值是未知的。C#为这种情形提供了另一种类型的变量只读字段。readonly 关键字比 const 灵活,允许把一个字段设置为常量,但还需要执行一些计算,以确定它的初始值。其规则是可以在构造函数中给只读段赋值,但不能在其他地方赋值。只读字段可以是一个实例字段,而不是静态字段,类的每个实例可以有不同的值。与const字段不同,如果要把只读字段设置为静态,就必须显式声明。 ```csharp // 实例只读字段 readonly double taxRate;

// 静态字段字段 static readonly double taxRate1;

BasicsTest() { // 只读字段可以在声明时赋值,也可以在构造函数中赋值 taxRate = 0.8; } static BasicsTest() { // 静态只读字段在类的静态构造函数中赋值 taxRate1 = 0.9; }

  1. <a name="fa109610"></a>
  2. ## 常量和只读字段的区别
  3. - 常量只能在声明语句中初始化,而且必须初始化,初始化之后在任何地方都不能改变; `readonly` 字段既可以在声明时初始化,也可以在构造函数中改变它的值。如果是 `实例只读字段` ,可以在 实例构造函数中改变它的值,如果是 `静态只读字段` ,则可以在静态构造函数中改变它的值
  4. - 常量的值必须在编译时决定,编译完成之后它的值就被替换为字面量; `readonly` 字段的值可以在运行时决定,可以在不同的构造函数中设置不同的值
  5. - 常量总是像静态字段,在类的外部要通过 `类名.常量名` 的方式访问; `readonly` 字段既可以是静态字段,也可以是实例字段
  6. - 常量在内存中没有存储位置,而 `readonly` 字段在内存中有存储位置
  7. <a name="b1f2a608"></a>
  8. ## 值类型和引用类型空值
  9. 默认情况下,引用类型在未初始化时具有空值。
  10. **例如:**一个字符串变量(或引用类型数据类型的任何其他变量),但没有赋值。在这种情况下,它具有空值,这意味着它不指向任何其他内存位置,因为它还没有值。值类型变量不能为 `null` ,因为它包含值而不是内存地址。所以必须在使用前为值类型变量分配值。
  11. <a name="658a3b7a"></a>
  12. ## 可空类型
  13. `C#2.0` 为值类型引入了可空类型,允许为值类型变量赋值 `null` 或声明值类型变量而不为其赋值。
  14. ```csharp
  15. int? age = null;

类型转换

C# 是一门强类型语言,对类型要求比较严格。但是在一定的条件下是可以相互转换的,如将 int 型数据转换成 double 型数据。C# 允许使用两种转换方式:隐式转换和显式转换。

隐式转换

隐式转换是系统默认的,不需要加以声明就会自动执行隐式类型转换,在隐式转换过程,编译器无需对转换进行详细检查就能够安全的执行。隐式类型转换是从低精度数值类型=>高精度数值类型

  1. int a = 10;
  2. double b = a;//自动隐式类型转换

显式转换

将高精度值=>低精度进行数据转换时,可能会丢失数据,这时候需要使用显式转换。并且要考虑到可能出现算术溢出;显式转换需要明确指出要转换的类型。

显式转换可能导致错误,进行这种转换时编译器会对转换进行溢出检测,如果有溢出说明转换失败,表示源类型不是一个合法的目标类型无法进行类型转换;强制类型转换会造成数据精度丢失。

  1. double a = 10;
  2. int b = (int)a;//显式将double类型转换为int

可空类型数据转换=>非可空类型或者另一个可空类型,其中可能会丢失数据,就必须使用显式类型转换。并且如果从可空类型转换为非可空类型时变量值为null,会抛出 InvalidOperationException 异常。

  1. int? a = null;
  2. int b = (int)a;
  3. //System.InvalidOperationException:“可为空的对象必须具有一个值。

通过方法进行转换

ToString()

C#中的类型基类都继承自 Object 类,所以都可以使用 ToString() 来转换成字符串。

  1. int a = 10;
  2. string s = a.ToString();

Int.Parse()

用于将 string 类型参数转换为 int ,注意: string 类型参数不能为 null ,并且也只能是各种整型,不能是浮点型。

  1. string a = "2";
  2. string b = "2.6";
  3. string c = null;
  4. int a1 = int.Parse(a);//正常
  5. int a2 = int.Parse(b);//错误:输入字符串格式错误
  6. int a3 = int.Parse(c);//值不能为null

Int.TryParse()

该方法与 Int.Parse() 方法类似,不同点在于 Int.Parse() 方法无法转换成功时会抛出异常。而 Int.TryParse() 方法在无法进行转换时会返回 falseInt.TryParse() 方法需要一个 out 类型的参数,如果转换成功, out 参数的值就是正常转换的值,否则返回 false

  1. string a = "2";
  2. string b = "2.6";
  3. string c = null;
  4. int i;
  5. bool a1 = int.TryParse(aout i);//转换成功,i=2
  6. bool a2 = int.TryParse(b out i);//转换失败,a2=false
  7. bool a3 = int.TryParse(c out i);//转换失败,a3=false

通过Convert类进行转换

  1. string a = "2";
  2. int a1 = Convert.ToInt32(a);
方法 说明
Convert.ToInt32() 转换为整型(int)
Convert.ToChar() 转换为字符型(char)
Convert.ToString() 转换为字符串型(string)
Convert.ToDateTime() 转换为日期型(datetime)
Convert.ToDouble() 转换为双精度浮点型(double)
Conert.ToSingle() 转换为单精度浮点型(float)

实现自定义转换

通过继承接口 IConventible 或者 TypeConventer 类,可以实现自定义转换。

使用 as 运算符转换

使用 as 操作符转换,但是 as 只能用于引用类型和可为空的类型。使用 as 有很多好处,当无法进行类型转换时,会将对象赋值为 NULL ,避免类型转换时报错或是出异常。C#抛出异常在进行捕获异常并进行处理是很消耗资源的,如果只是将对象赋值为 NULL 的话是几乎不消耗资源的(消耗很小的资源)。

  1. object o = "abc";
  2. string s = o as string; //执行第一次类型兼容性检查,并返回结果
  3. if (s != null)
  4. Console.WriteLine("转换成功!");
  5. else
  6. Console.WriteLine("转换失败!");

装箱和拆箱

装箱和拆箱在值类型和引用类型之间架起了一座桥梁,使得任何 value-type 的值都可以转换为 object 类型的值,反之亦可。

装箱

装箱是指将一个值类型的数据隐式地转换成一个对象类型(object)的数据。执行装箱操作时不可避免的要在堆上申请内存空间,并将堆栈上的值类型数据复制到申请的堆内存空间上,这是要消耗内存和cpu资源的。在执行装箱转换时,也可以使用显式转换。

  1. int i = 0;
  2. object obj = i; //装箱:值类型转换为引用类型

拆箱

拆箱是指将一个对象类型的数据显式地转换成一个值类型数据。拆箱过程是装箱的逆过程,是将存储在堆上的引用类型值转换为值类型并赋给值类型变量。

拆箱操作分为两步

  1. 检查对象实例,确保它是给定值类型的一个装箱值
  2. 将该值从实例复制到值类型变量中
  1. int i = 0;
  2. object obj = i; //装箱:值类型转换为引用类型
  3. int j = (int)obj; //拆箱:引用类型转换为值类型

装箱拆箱注意事项:

  • 装箱可以隐式进行,但拆箱必须显式
  • 在装箱的时候,并不需要显式类型转换。但在拆箱时需要类型转换。因为在拆箱时对象可以被转换为任何类型
  • 装什么拆什么,装箱就是要在托管堆重开辟空间,不但要装数值而且还要装类型。所以说装什么拆什么,也就是用什么值类型装箱,就要用什么值类型拆箱

常见运算符

三元运算符

if/else 的简化形式。首先判断一个条件,如果为真返回第一个值,为假返回后一个值。

  1. int a = 3;
  2. bool result = a > 10 ? true : false; //a>10?如果大于返回true否则返回false

checked/unchecked

如果把代码块标记为 checkedCLR 就会执行栈溢出检测,如果要禁止栈溢出,则可以把代码标记 unchecked

  1. //byte类型最大取值255
  2. byte a = 255;
  3. checked
  4. {
  5. a++;
  6. }
  7. //这里如果不加checed.++后输出0(不会抛异常,但会丢失数据,溢出的位会被舍弃,所以值为0),加上后会抛出栈溢出异常
  8. Console.WriteLine(a);

is

is 运算符可以检测对象是否与特定类型兼容,兼容表示对象是该类型或者派生自该类型。

转换规则如下:

  • 检查对象类型的兼容性,并返回结果 true/false
  • 不会抛出异常
  • 如果对象为null,刚返回false
    1. object o = "abc";
    2. if (o is string) //执行第一次类型兼容性检查
    3. {
    4. string s = (string)o; //执行第二次类型兼容性检查,并转换
    5. Console.WriteLine("转换成功!");
    6. }
    7. else
    8. {
    9. Console.WriteLine("转换失败!");
    10. }

as

转换规则如下

  • 检查对象类型的兼容性并返回转换结果,如果不兼容则返回null
  • 不会抛出异常
  • 如果结果判断为空,则强制执行类型转换将抛出 NullReferenceException 异常
    1. object o = "abc";
    2. string s = o as string; //执行第一次类型兼容性检查,并返回结果
    3. if (s != null)
    4. Console.WriteLine("转换成功!");
    5. else
    6. Console.WriteLine("转换失败!");

sizeof

sizeof 运算符可以确定栈中值类型需要的长度(单位为字节)。

  1. Console.WriteLine(sizeof(int));//4个字节
  2. Console.WriteLine(sizeof(byte));//1个字节

typeof

返回一个表示特定类型的 System.Type 对象。

  1. Console.WriteLine(typeof(int));//System.Int32
  2. Console.WriteLine(typeof(byte));//System.Byte

可空类型和运算符

C# 2.0 中出现了可空类型,允许值类型也可以为空 null,可空类型的实现基于 C#泛型。

注意
在程序中使用可空类型就必须考虑 null 值在各种运算符一起使用的影响,通常可空类型与一元或二元运算符一起使用时,如果一个操作数为 null 或两个操作数为 null ,结果就是 null

  1. int? a = null;
  2. int? c = a + 4; //c=null

空合并运算符

空合并运算符 ?? 提供了快捷方式处理可空类型和引用类型时表示 null 可能的值。

注意:
只能针对引用类型处理,规则是:

  • 如果第一个操作数不是null,值就等于第一个操作数的值
  • 如果第一个操作数是null,值就等于第二个操作数的值
    1. int? a = null;
    2. int b;
    3. b = a ?? 10;//第一个操作数是null,值为第二个操作数.10
    4. a = 3;
    5. b = a ?? 10;//第一个操作数不是null,值为第一个操作数.3

关键字

C#包含保留字,对编译器有特殊意义。这些保留字称为“关键字”。关键字不能用作变量,类,接口等的名称(标识符),关键字不能用作标识符(变量名,类,接口等)。但是,它们可以与前缀“@”一起使用。例如,class 是保留关键字,因此不能用作标识符,但可以使用 @class

🎨 关键字的更多信息,访问 MSDN

枚举

enum 是值类型数据类型。枚举用于声明命名整数常量的列表。可以直接在命名空间,类或结构中使用enum关键字定义。

  • 枚举用于为每个常量指定一个名称,以便可以使用其名称引用常量整数默认情况下,枚举的第一个成员的值为 0,每个连续的枚举成员的值增加 1
  • 枚举可以包括数字数据类型的命名常量,例如 bytesbyteshortushortintuintlongulong
  • 枚举不能与字符串类型一起使用

Enum 是一个抽象类,包含用于枚举的静态帮助器方法

Enum method Description
Format 将指定的枚举类型值转换为指定的字符串格式
GetName 返回指定枚举的指定值的常量的名称
GetNames 返回指定枚举的所有常量的字符串名称数组
GetValues 返回指定枚举的所有常量值的数组
object Parse(type, string) 将一个或多个枚举常量的名称或数值的字符串表示形式转换为等效的枚举对象
bool TryParse(string, out TEnum) 将一个或多个枚举常量的名称或数值的字符串表示形式转换为等效的枚举对象,返回值表示转换是否成功
  1. enum Color
  2. {
  3. Red
  4. Green
  5. Blue
  6. }

预处理器指令

  • #region/#endregion 指令用于把一段代码标记为有给定名称的一个块
  • #define/#undef 结合 #if/#elif/endif 实现条件编译
  • …. ```csharp

    define debug

    using System;

namespace CSharp.Study.Test { class Program { static void Main(string[] args) {

if debug

  1. Console.WriteLine("debug");

else

  1. Console.WriteLine("other");

endif

  1. }
  2. }

} ```