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