C# 语言引用类型
C#
中有两种类型:引用类型和值类型。引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。对于引用类型,两种变量可引用同一对象。因此,对一个变量执行的操作会影响另一个变量所引用的对象。对于值类型,每个变量都具有其自己的数据副本,对一个变量执行的操作不会影响另一个变量(in
、ref
和 out
参数变量除外)。
内置应用类型
C#
具有多个内置引用类型。 这些类型包含的关键字或运算符是 .NET
库中的类型的同义词。
对象类型
object
类型是System.Object
在 .NET
中的别名。 在 C#
的统一类型系统中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都是直接或间接从 System.Object
继承的。可以将任何类型的值赋给 object
类型的变量。 可以使用文本 null
将任何 object
变量赋默认值。
& 将值类型的变量转换为对象的过程称为 装箱。将 object
类型的变量转换为值类型的过程称为 拆箱。
字符串类型
string
类型表示零个或多个 Unicode
字符的序列。string
是 System.String
在 .NET
中的别名。尽管 string
为引用类型,但是定义相等运算符 ==
和 !=
是为了比较 string
对象(而不是引用)的值。 这使得对字符串相等性的测试更为直观。例如:
string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));
此时将显示“True”
,然后显示“False”
,因为字符串的内容是相等的,但 a
和 b
并不指代同一字符串实例。
+
运算符连接字符串:
string a = "good " + "morning";
上面的示例将创建包含“good morning”
的字符串对象。
字符串是不可变的,即:字符串对象在创建后,尽管从语法上看似乎可以更改其内容,但事实上并不可行。 例如下面的例子:编译器实际上会创建一个新的字符串对象来保存新的字符序列,且该新对象将赋给 b
。 已为 b
分配的内存(当它包含字符串“h”
时)可用于垃圾回收。
string b = "h";
b += "ello";
[]
运算符可用于只读访问字符串的个别字符。有效索引于 0
开始,且必须小于字符串的长度:
string str = "test";
char x = str[2]; // x = 's';
同样, []
运算符也可用于循环访问字符串中的每个字符:
string str = "test";
for (int i = 0; i < str.Length; i++)
{
Console.Write(str[i] + " ");
}
// 输出: t e s t
字符串文本属于 string
类型且可以两种形式编写(带引号和仅 @
带引号)。带引号字符串括在双引号 (")
内。
"good morning" // 字符串
字符串文本可包含任何字符文本。包括转义序列。下面的示例使用转义序列 \\
表示反斜杠,使用 \u0066
表示字母 f
,以及使用 \n
表示换行符。
string a = "\\\u0066\n F";
Console.WriteLine(a);
// 输出:
// \f
// F
逐字字符串文本以 @
开头,并且也括在双引号内。 例如:
@"good morning" // 字符串
逐字字符串的优点是不处理转义序列,这样就可轻松编写完全限定的 Windows
文件名等:
@"c:\Docs\Source\a.txt"
若要在 @-quoted
字符串中包含双引号,双倍添加即可:
@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.
委托类型
委托类型的声明与方法签名相似。它有一个返回值和任意数目任意类型的参数:
public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);
在 .NET
中, System.Action
和 System.Func
类型为许多常见委托提供泛型定义。可能不需要定义新的自定义委托类型。相反,可以创建提供的泛型类型的实例化。
delegate
是一种可用于封装命名方法或匿名方法的引用类型。委托类似于 C++
中的函数指针;但是,委托是类型安全和可靠的。委托是事件的基础。通过将委托与命名方法或匿名方法关联,可以实例化委托。
必须使用具有兼容返回类型和输入参数的方法或 lambda
表达式实例化委托。为了与匿名方法一起使用,委托和与之关联的代码必须一起声明。
动态类型
dynamic
类型表示变量的使用和对其成员的引用绕过编译时类型检查,改为在运行时解析这些操作。dynamic
类型简化了对 COM API
(例如 Office Automation API
)、动态 API
(例如 IronPython
库)和 HTML
文档对象模型(DOM
) 的访问。
在大多数情况下, dynamic
类型与 object
类型的行为类似。具体而言,任何非 Null
表达式都可以转换为dynamic
类型。 dynamic
类型与 object
的不同之处在于,编译器不会对包含类型 dynamic
的表达式的操作进行解析或类型检查。编译器将有关该操作信息打包在一起,之后这些信息会用于在运行时评估操作。在此过程中,dynamic
类型的变量会编译为 object
类型的变量。 因此,dynamic
类型只在编译时存在,在运行时则不存在。
下面的示例将 dynamic
类型的变量与 object
类型的变量进行对比。若要在编译时验证每个变量的类型,请将鼠标指针放在 WriteLine
语句中的 dyn
或 obj
上。 请将下面的代码复制到可以使用 IntelliSense
的编辑器中。IntelliSense
会对 dyn
显示“dynamic”
,对 obj
显示“object”
。
class Program
{
static void Main(string[] args)
{
dynamic dyn = 1;
object obj = 1;
// Rest the mouse pointer over dyn and obj to see their
// types at compile time.
System.Console.WriteLine(dyn.GetType());
System.Console.WriteLine(obj.GetType());
}
}
WriteLine
语句显示 dyn
和 obj
的运行时类型。此时,两者的类型均为整数。将生成以下输出:
System.Int32
System.Int32
若要查看编译时 dyn
与 obj
之间的区别,请在前面示例的声明和 WriteLine
语句之间添加下列两行:
dyn = dyn + 3;
obj = obj + 3;
尝试在表达式 obj + 3
中添加整数和对象时,将报告编译器错误。但是,对于 dyn + 3
,不会报告任何错误。 在编译时不会检查包含 dyn
的表达式,原因是 dyn
的类型为 dynamic
。
下面的示例在多个声明中使用 dynamic
。 Main
方法也将编译时类型检查与运行时类型检查进行了对比。
using System;
namespace DynamicExamples
{
class Program
{
static void Main(string[] args)
{
ExampleClass ec = new ExampleClass();
Console.WriteLine(ec.exampleMethod(10));
Console.WriteLine(ec.exampleMethod("value"));
// The following line causes a compiler error because exampleMethod
// takes only one argument.
//Console.WriteLine(ec.exampleMethod(10, 4));
dynamic dynamic_ec = new ExampleClass();
Console.WriteLine(dynamic_ec.exampleMethod(10));
// Because dynamic_ec is dynamic, the following call to exampleMethod
// with two arguments does not produce an error at compile time.
// However, it does cause a run-time error.
//Console.WriteLine(dynamic_ec.exampleMethod(10, 4));
}
}
class ExampleClass
{
static dynamic field;
dynamic prop { get; set; }
public dynamic exampleMethod(dynamic d)
{
dynamic local = "Local variable";
int two = 2;
if (d is int)
{
return local;
}
else
{
return two;
}
}
}
}
// Results:
// Local variable
// 2
// Local variable
本篇文章简单的讲述了引用类型的定义与一些常用的内置引用类型,后续章节会对这些内置引用类型做比较详细的说明。