C Sharp/数据类型

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

值类型

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

struct

编辑

用户自定义的值类型:

struct Person
{
    public string Name;
    public int Height;
    public string Occupation;
}

public class StructWikiBookSample2
{
    public static void Main()
    {
        Person john = new Person { Name = "John", Height = 182, Occupation = "Programmer" };
    }
}

struct是值类型的容器。可以有构造函数、方法、实现接口。但不支持继承。总是有一个缺省构造函数。作为函数参数时传值。

从性能角度,建议struct不超过16个字节。

枚举

编辑

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;

装箱和拆箱

编辑

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

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

可空类型

编辑

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

int? a=null;

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

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);

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

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

引用类型

编辑

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

  1. CLR计算堆中需要的内存;
  2. CLR在堆中插入数据;
  3. CLR标记内存空间中的占用结尾标记;
  4. 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

整型字面量后缀(不区分大小写)u、l、ul,分别表示:uint、long、ulong。

字符字面量用单引号围起来。内容可以是转义序列,可以用\x前缀后跟1至4个十六进制数字,或者\u后跟4个十六进制数字。

整型字面量只允许十进制或者十六进制(0x前缀)。

浮点和定点

编辑
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

字面量后缀(不区分大小写)m、d、f,分别表示:decimal、double、float。

浮点字面量或者是带小数点的定点表示,如3.14;或者是科学计数法。

整型将被隐式转换为 decimal 类型。浮点型和 decimal 类型之间不存在隐式转换;因此,必须使用强制转换以在这两个类型之间转换。可以在同一表达式中混合使用 decimal 和数值整型。但是,不进行强制转换就混合使用 decimal 和浮点型将导致编译错误。通过使用 String.Format 方法或Console.Write 方法(其调用String.Format())来设置结果的格式。 货币格式是使用标准货币格式字符串“C”或“c”指定的

在CLR中,Decimal类型不是基元类型。这就意味着CLR没有知道如何处理Decimal的IL指令。

其他预定义类型

编辑
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.

字符串类型

编辑

string是System.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对象,除非用字符数组初始化

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类用于创建一个字符缓冲区,在此上可以逐个字符的修改、追加。适用于连续追加上百次操作的情形。

接口

编辑
主页面:C Sharp/Interfaces

使用interface关键字声明接口。它类似于类声明。默认情况下,接口语句是public的。例如:

public interface ITransactions {
   // interface members
   void showTransaction();
   double getAmount();
}

装箱类型

编辑

委托

编辑
主页面: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;
		}
	}

自定义类

编辑

数组

编辑

数组的元素具有相同的类型。数组类型的基类型是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} };

指针

编辑

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;

在同一个声明中声明多个指针时,星号 * 仅与基础类型一起写入;而不是用作每个指针名称的前缀。 例如:

int* p1, p2, p3;     // 正确  
int *p1, *p2, *p3;   // 错误

c#中指向堆内存的指针必须在unsaved和fixed的上下文中使用。获取数组空间的指针,需要使用fixed关键字:

       int[] a = { 1, 2, 3 };
            unsafe
            {
                fixed (int* p = a) {
                    //do something...
                };
            }

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), http://msdn.microsoft.com/en-us/library/y31yhkeb.aspx