C# 语言引用类型

C# 中有两种类型:引用类型和值类型。引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。对于引用类型,两种变量可引用同一对象。因此,对一个变量执行的操作会影响另一个变量所引用的对象。对于值类型,每个变量都具有其自己的数据副本,对一个变量执行的操作不会影响另一个变量(inrefout 参数变量除外)。

内置应用类型

C# 具有多个内置引用类型。 这些类型包含的关键字或运算符是 .NET 库中的类型的同义词。

对象类型

object 类型是System.Object.NET 中的别名。 在 C# 的统一类型系统中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都是直接或间接从 System.Object 继承的。可以将任何类型的值赋给 object 类型的变量。 可以使用文本 null 将任何 object 变量赋默认值。

& 将值类型的变量转换为对象的过程称为 装箱。将 object 类型的变量转换为值类型的过程称为 拆箱

字符串类型

string 类型表示零个或多个 Unicode 字符的序列。stringSystem.String.NET 中的别名。尽管 string 为引用类型,但是定义相等运算符 ==!= 是为了比较 string 对象(而不是引用)的值。 这使得对字符串相等性的测试更为直观。例如:

  1. string a = "hello";
  2. string b = "h";
  3. // Append to contents of 'b'
  4. b += "ello";
  5. Console.WriteLine(a == b);
  6. Console.WriteLine(object.ReferenceEquals(a, b));

此时将显示“True”,然后显示“False”,因为字符串的内容是相等的,但 ab 并不指代同一字符串实例。

+ 运算符连接字符串:

  1. string a = "good " + "morning";

上面的示例将创建包含“good morning”的字符串对象。

字符串是不可变的,即:字符串对象在创建后,尽管从语法上看似乎可以更改其内容,但事实上并不可行。 例如下面的例子:编译器实际上会创建一个新的字符串对象来保存新的字符序列,且该新对象将赋给 b 。 已为 b 分配的内存(当它包含字符串“h”时)可用于垃圾回收。

  1. string b = "h";
  2. b += "ello";

[] 运算符可用于只读访问字符串的个别字符。有效索引于 0 开始,且必须小于字符串的长度:

  1. string str = "test";
  2. char x = str[2]; // x = 's';

同样, [] 运算符也可用于循环访问字符串中的每个字符:

  1. string str = "test";
  2. for (int i = 0; i < str.Length; i++)
  3. {
  4. Console.Write(str[i] + " ");
  5. }
  6. // 输出: t e s t

字符串文本属于 string 类型且可以两种形式编写(带引号@ 带引号)。带引号字符串括在双引号 (") 内。

  1. "good morning" // 字符串

字符串文本可包含任何字符文本。包括转义序列。下面的示例使用转义序列 \\ 表示反斜杠,使用 \u0066 表示字母 f,以及使用 \n表示换行符。

  1. string a = "\\\u0066\n F";
  2. Console.WriteLine(a);
  3. // 输出:
  4. // \f
  5. // F

逐字字符串文本以 @ 开头,并且也括在双引号内。 例如:

  1. @"good morning" // 字符串

逐字字符串的优点是不处理转义序列,这样就可轻松编写完全限定的 Windows 文件名等:

  1. @"c:\Docs\Source\a.txt"

若要在 @-quoted 字符串中包含双引号,双倍添加即可:

  1. @"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

委托类型

委托类型的声明与方法签名相似。它有一个返回值任意数目任意类型的参数

  1. public delegate void MessageDelegate(string message);
  2. public delegate int AnotherDelegate(MyType m, long num);

.NET 中, System.ActionSystem.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 语句中的 dynobj 上。 请将下面的代码复制到可以使用 IntelliSense 的编辑器中。IntelliSense 会对 dyn 显示“dynamic”,对 obj 显示“object”

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. dynamic dyn = 1;
  6. object obj = 1;
  7. // Rest the mouse pointer over dyn and obj to see their
  8. // types at compile time.
  9. System.Console.WriteLine(dyn.GetType());
  10. System.Console.WriteLine(obj.GetType());
  11. }
  12. }

WriteLine 语句显示 dynobj 的运行时类型。此时,两者的类型均为整数。将生成以下输出:

  1. System.Int32
  2. System.Int32

若要查看编译时 dynobj 之间的区别,请在前面示例的声明和 WriteLine 语句之间添加下列两行:

  1. dyn = dyn + 3;
  2. obj = obj + 3;

尝试在表达式 obj + 3 中添加整数和对象时,将报告编译器错误。但是,对于 dyn + 3 ,不会报告任何错误。 在编译时不会检查包含 dyn 的表达式,原因是 dyn 的类型为 dynamic

下面的示例在多个声明中使用 dynamicMain 方法也将编译时类型检查与运行时类型检查进行了对比。

  1. using System;
  2. namespace DynamicExamples
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. ExampleClass ec = new ExampleClass();
  9. Console.WriteLine(ec.exampleMethod(10));
  10. Console.WriteLine(ec.exampleMethod("value"));
  11. // The following line causes a compiler error because exampleMethod
  12. // takes only one argument.
  13. //Console.WriteLine(ec.exampleMethod(10, 4));
  14. dynamic dynamic_ec = new ExampleClass();
  15. Console.WriteLine(dynamic_ec.exampleMethod(10));
  16. // Because dynamic_ec is dynamic, the following call to exampleMethod
  17. // with two arguments does not produce an error at compile time.
  18. // However, it does cause a run-time error.
  19. //Console.WriteLine(dynamic_ec.exampleMethod(10, 4));
  20. }
  21. }
  22. class ExampleClass
  23. {
  24. static dynamic field;
  25. dynamic prop { get; set; }
  26. public dynamic exampleMethod(dynamic d)
  27. {
  28. dynamic local = "Local variable";
  29. int two = 2;
  30. if (d is int)
  31. {
  32. return local;
  33. }
  34. else
  35. {
  36. return two;
  37. }
  38. }
  39. }
  40. }
  41. // Results:
  42. // Local variable
  43. // 2
  44. // Local variable

本篇文章简单的讲述了引用类型的定义与一些常用的内置引用类型,后续章节会对这些内置引用类型做比较详细的说明。