C Sharp/数据类型

C#是静态类型语言。即每个变量和常量都有固定的类型。类型分为值类型与引用类型两大类。值类型变量存放在栈上;引用类型在堆中分配空间。内建类型外,也可以定制类型。

值类型编辑

  • 函数参数传值。
  • 值类型没有/不需要调用构造函数。总是自动初始化。
  • 值类型的字段初始化为0或null.
  • 值类型不能赋值为null值,但可用Nullable类型
  • 值类型可以装箱。
  • 存储在栈中。声明一个值类型变量,则立即分配内存。如果变量出了作用域,则自动销毁。
  • 值类型包括简单类型、枚举类型和结构类型。

struct编辑

用户自定义的值类型:

struct Foo
{
    ...
}

装箱和拆箱编辑

装箱(Boxing)就是从值类型到引用类型的转换。[1]装箱在C#是隐式的。拆箱(unboxing)是从引用类型到值类型的转换。拆箱需要显式的类型cast。

 class Test
    {
        static void Mian()
        {
            int i = 3;
            object a = i;//装箱
            int j = (int)a;//拆箱
        }
    }

可空值类型编辑

可空值类型(nullable value type) T?表示该类型还可以取空值。例如:

int? a=null;

任何可空值类型T?是泛型结构System.Nullable<T>的实例。System.Nullable<T>有两个只读属性:Value与HasValue。

从可空值类型转化为基础值类型,可用空值结合运算符??或者System.Nullable<T>.GetValueOrDefault()。例如:

int? c = null;
int d = c ?? -1;
Console.WriteLine($"d is {d}");  // output: d is -1

引用类型编辑

引用类型的变量指向堆中的一块空间。为受管的引用。构造函数调用时,在堆上分配创建一个对象,并把它赋给变量。CLR执行下述4步:

  1. The CLR计算堆中需要的内存;
  2. The CLR在堆中插入数据;
  3. The CLR标记内存空间中的占用结尾标记;
  4. The CLR返回新创建空间的引用

当引用类型的变量出了作用域,则引用失效(broken)。如果一个对象没有引用,则被标记为垃圾。垃圾回收器随后会收集、摧毁它。

引用变量如果为null,则它不引用任何对象。

引用类型包括:

  • 接口
    • 装箱类型
    • 委托
    • 自定义类
  • 数组
  • 指针

说明:尽管string是类,但如果用到了相等运算符==或者!=时则表示比较string对象的值。

定制类型编辑

定制的值类型使用struct或enum关键字。定制的引用类型使用class关键字。

.NET框架中对应的类型编辑

// C#
public void UsingCSharpTypeAlias()
{
  int i = 42;
}

public void EquivalentCodeWithoutAlias()
{
  System.Int32 i = 42;
}
 ' Visual Basic .NET
 Public Sub UsingVisualBasicTypeAlias()
   Dim i As Integer = 42
 End Sub

 Public Sub EquivalentCodeWithoutAlias()
   Dim i As System.Int32 = 42
 End Sub

值类型的长度是固定的。引用类型都是继承自object类,其长度随平台而定。

.NET框架的类型有成员方法,如:

int i = 97;
string s = i.ToString();  // The value of s is now the string "97".

System.Int32类型实现了Parse()方法:

string s = "97";
int i = int.Parse(s); // The value of i is now the integer 97.

值类型可以装箱,然后可以拆箱:

object boxedInteger = 97;
int unboxedInteger = (int) boxedInteger;

装箱与拆箱不是类型安全的。编译器不会产生报错,弹运行时可能爆异常:

object getInteger = "97";
int anInteger = (int) getInteger; // No compile-time error. The program will crash, however.

C#内建类型与对应的.NET框架类型列表:

整型编辑

C# 别名 .NET 类型 比特长度 值域
sbyte System.SByte 8 -128 to 127
byte System.Byte 8 0 to 255
short System.Int16 16 -32,768 to 32,767
ushort System.UInt16 16 0 to 65,535
char System.Char 16 A unicode character of code 0 to 65,535
int System.Int32 32 -2,147,483,648 to 2,147,483,647
uint System.UInt32 32 0 to 4,294,967,295
long System.Int64 64 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
ulong System.UInt64 64 0 to 18,446,744,073,709,551,615

浮点编辑

C# 别名 .NET类型 比特长度 精度 值域
float System.Single 32 7 digits 1.5 x 10-45 to 3.4 x 1038
double System.Double 64 15-16 digits 5.0 x 10-324 to 1.7 x 10308
decimal System.Decimal 128 28-29 decimal places 1.0 x 10-28 to 7.9 x 1028

其他预定义类型编辑

C# 类型 .NET 类型 比特长度 值域
bool System.Boolean 32 true or false, which aren't related to any integer in C#.
object System.Object 32/64 Platform dependent (a pointer to an object).
string System.String 16*length A unicode string with no special upper bound.

委托和事件编辑

主页面:w:Delegate (CLI)

委托和事件的变量,可以看作是一个或多个面向对象的函数指针绑定到一个变量上。

delegate void MouseEventHandler(object sender, MouseEventArgs e);

public class Button : System.Windows.Controls.Control
{
    private event MouseEventHandler _onClick;

    /* Imaginary trigger function */
    void Click()
    {
        _onClick(this, new MouseEventArgs(data));
    }
}

类中声明的事件,只能在作为事件的拥有者的该类中才能调用。

public class MainWindow : System.Windows.Controls.Window
{
    private Button _button1;

    public MainWindow()
    {
        _button1 = new Button();
        _button1.Text = "Click me!";

        /* Subscribe to the event */
        _button1.ClickEvent += Button1_OnClick;

        /* Alternate syntax that is considered old:
        _button1.MouseClick += new MouseEventHandler(Button1_OnClick); */
    }

    protected void Button1_OnClick(object sender, MouseEventArgs e)
    {
        MessageBox.Show("Clicked!");
    }
}

也可以做定制事件实现:

	private EventHandler _clickHandles = (s, e) => { };

	public event EventHandler Click
	{
		add
		{
			// Some code to run when handler is added...
			...

			_clickHandles += value;
		}
		remove
		{
			// Some code to run when handler is removed...
			...

			_clickHandles -= value;
		}
	}

枚举编辑

enums是代表整数的命名的值:

enum Season
{
    Winter = 0,
    Spring = 1,
    Summer = 2,
    Autumn = 3,
    Fall = Autumn    // Autumn is called Fall in American English.
}

enum变量默认初始化为0:

Season season;
season = Season.Spring;

enum类型是整数值,允许加减运算。但乘除运算需要显式cast。枚举类型和整型的相互转换,也需要显式cast。

season = (Season)2;  // cast 2 to an enum-value of type Season.
season = season + 1; // Adds 1 to the value.
season = season + season2; // Adding the values of two enum variables.
int value = (int)season; // Casting enum-value to integer value.

season++; // Season.Spring (1) becomes Season.Summer (2).
season--; // Season.Summer (2) becomes Season.Spring (1).

枚举类型可以按位或操作:

Color myColors = Color.Green | Color.Yellow | Color.Blue;

字符串类型编辑

string是System.String的别名。string (System.String)不是一个struct因此不是基本类型。

string表示一个不可变的unicode字符序列。string可包含多个\x0字符。

System.StringBuilder类可用做“可变的”字符串。

var sb = new StringBuilder();
sb.Append('H');
sb.Append("el");
sb.AppendLine("lo!");
// Declare without initializing.
string message1;

// Initialize to null.
string message2 = null;

// Initialize as an empty string.
// Use the Empty constant instead of the literal "".
string message3 = System.String.Empty;

// Initialize with a regular string literal.
string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";

// Initialize with a verbatim string literal.
string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";

// Use System.String if you prefer.
System.String greeting = "Hello World!";

// In local variables (i.e. within a method body)
// you can use implicit typing.
var temp = "I'm still a strongly-typed System.String!";

// Use a const string to prevent 'message4' from
// being used to store another string value.
const string message4 = "You can't get rid of me!";

// Use the String constructor only when creating
// a string from a char*, char[], or sbyte*. See
// System.String documentation for details.
char[] letters = { 'A', 'B', 'C' };
string alphabet = new string(letters);//不要使用new运算符创建string对象出发用字符数组初始化

IsNullOrEmpty(message4);//为避免NullReferenceException 

//String.Format利用占位符来构造格式字符串。如:
var pw = (firstName: "Phillis", lastName: "Wheatley", born: 1753, published: 1773);
Console.WriteLine("{0} {1} was an African American poet born in {2}.", pw.firstName, pw.lastName, pw.born);

字符串对象是immutable

字符串字面量:

  • 用引号的字符串字面量(Quoted string literals)
  • 逐字的字符串字面量(Verbatim string literals):用@符号作为前缀。更适合多行、包含反斜线或嵌入的双引号(用两个双引号表示)等情形。
  • 原始字符串字面量(Raw string literals):从C#11.0开始,以连续3个双引号开始和结束的字符串。对于多行情形,要求开始和结束的3个连续双引号必须各自独占一行,该行前缀之后、后缀之前的字符都被忽略。

转义字符序列需要注意:\u、\U、\x分别开始4位、8位、0至4位的16进制数字序列,对应Unicode的码位。

C#6开始支持格式化(插值)字符串以$号为前缀,插值表达式写在大括号中。是String.Format方法的简化形式。语法为:

{<interpolatedExpression>[,<alignment>][:<formatString>]}

从C#10.0开始,可以用插值字符串初始化constant 字符串。从C#11.0开始,原始字符串中也可用插值字符串,并在原始字符串的前缀之前的$号的个数来表示插值表达式用多少层大括号包围。逐字字符串字面量如果用$@ 或 @$作为前缀也可以使用插值表达式。

空字符串对象是String.Empty,即""对应的0长度字符串对象。null字符串不是指引到System.String的一个对象,调用其方法可引发NullReferenceException. 但可以用null字符串与其他字符串连接或比较操作。

方法String.Join可以把数组的多个元素连接为一个字符串并用指定的分隔符隔开。

StringBuilder类用于创建一个字符缓冲区,在此上可以逐个字符的修改、追加。适用于连续追加上百次操作的情形。

数组编辑

数组的元素具有相同的类型。数组类型的基类型是System.Array。

数组每个维度的长度可以不声明:

string[] a_str;

给数组变量赋值时,要指明每个维度的长度:

a_str = new string[5];

声明与初始化可以写在一起:

string[] a_str = new string[5];

数组是传引用。下例中数组的两个元素的内容交换了:

static void swap (int[] a_iArray, int iI, int iJ)
{
    int iTemp = a_iArray[iI];

    a_iArray[iI] = a_iArray[iJ];
    a_iArray[iJ] = iTemp;
}

C#的数组对应于C语言的动态数组。在运行时确定数组长度:

int[] numbers = new int[2];
numbers[0] = 2;
numbers[1] = 5;
int x = numbers[0];


数组初始化器编辑

// Long syntax
int[] numbers = new int[5]{ 20, 1, 42, 15, 34 };
// Short syntax
int[] numbers2 = { 20, 1, 42, 15, 34 };
// Inferred syntax
var numbers3 = new[] { 20, 1, 42, 15, 34 };

===多维数组=== .

int[,] numbers = new int[3, 3];
numbers[1,2] = 2;

int[,] numbers2 = new int[3, 3] { {2, 3, 2}, {1, 2, 6}, {2, 4, 5} };


Nullable类型编辑

C# 2.0引入了可空类型。允许值类型为null(用于适配数据库)。

int? n = 2;
n = null;

Console.WriteLine(n.HasValue);

实际上这是用Nullable<T> struct实现的:

Nullable<int> n = 2;
n = null;

Console.WriteLine(n.HasValue);

指针编辑

C#允许在“unsafe”上下文中像C语言那样使用指针,不做运行时安全检查。只有如下类型可以使用指针:一些基本类型, enums, string, pointer, 只包含了允许类型的arrays和struct。[2]

static void Main(string[] args)
{
    unsafe
    {
        int a = 2;
        int* b = &a;

        Console.WriteLine("Address of a: {0}. Value: {1}", (int)&a, a);
        Console.WriteLine("Address of b: {0}. Value: {1}. Value of *b: {2}", (int)&b, (int)b, *b);

        // Will output something like:
        // Address of a: 71953600. Value: 2
        // Address of b: 71953596. Value: 71953600. Value of *b: 2
    }
}

Struct只能是纯结构,不能有受管的引用类型,如string或其他class:

public struct MyStruct
{
    public char Character;
    public int Integer;
}

public struct MyContainerStruct
{
    public byte Byte;
    public MyStruct MyStruct;
}

使用:

MyContainerStruct x;
MyContainerStruct* ptr = &x;

byte value = ptr->Byte;

Dynamic编辑

C Sharp 4.0和w:.NET Framework 4.0引入了dynamic关键字,用于运行时动态确定类型。dynamic被编译后是一个Object类型,编译器编译时不会对dynamic进行类型检查。

dynamic x = new Foo();
x.DoSomething();  // Will compile and resolved at runtime. An exception will be thrown if invalid.

匿名类型编辑

C# 3.0引入匿名类型。是静态编译时的语法糖。用于表示复杂的数据类型,特别是匿名函数或LINQ查询。

var carl = new { Name = "Carl", Age = 35 }; // Name of the type is only known by the compiler.
var mary = new { Name = "Mary", Age = 22 }; // Same type as the expression above
  1. Archer, Part 2, Chapter 4:The Type System
  2. Pointer types (C# Programming Guide)