object 类型

object 类型是 System . Object 在 .NET 中的别名。 在 C# 的统一类型系统中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都是直接或间接从 System . Object 继承的。 可以将任何类型的值(除 ref struct 外)分配给类型的 object 变量。 可以使用文本 null 将任何 object 变量赋值给其默认值。将值类型的变量转换为对象的过程称为装箱(boxed)。 将 object 类型的变量转换为值类型的过程称为取消装箱(拆箱,unboxed)。

字符串类型

string 类型表示零个或多个 Unicode 字符的序列。 string 是 System . String 在 .NET 中的别名。

尽管 string 为引用类型,但是定义相等运算符 “==” 和 “!=” 是为了比较 string 对象(而不是引用)的值。 基于值的相等性使得对字符串相等性的测试更为直观。 例如:

string a = "hello";
string b = "h";
// 向 “b” 添加内容
b += "ello";
Console . WriteLine ( a == b ); // 返回 True
Console . WriteLine ( object . ReferenceEquals ( a , b ) ); // 返回 False

“==” 返回 True 是因为两个 string 确实是一个值(都是 “hello”);object . ReferenceEquals 返回 False 是因为两个 string 确实没有引用同一个 string 实例。

“+” 操作符连接字符串:
string a = "value " + "type"
前面的代码创建了一个包含 “value type” 的字符串对象。

string 是不可变的 - 在对象创建之后,string 对象的内容不能被改变。例如,当您编写这段代码时,编译器实际上创建了一个新的 string 对象来保存新的字符序列,并将该新对象分配给 zfcb。已经为 zfcb 分配的内存(当它包含字符串 “h” 时)然后有资格进行垃圾收集。

string zfcb = "h";
zfcb += "ello";

“[ ]”操作符可用于对字符串中的单个字符进行只读访问。有效的索引值从 0 开始,并且必须小于字符串的长度:

string zfc = "我的家庭";
char zf = zfc [ 2 ];  // x = '家';

类似地,“[ ]” 操作符也可用于遍历字符串中的每个字符:

string zfc = "我的家庭";

for ( int i = 0 ; i < zfc . Length ; i++)
{
  Console . Write ( zfc [ i ] + " ");
}
// Output: 我 的 家 庭

string 字面值

字符串字面值是字符串类型,可以写成三种形式:原始的(raw)、引用的(quoted)和逐字的(verbatim)。

原始字符串字面量从 C# 11 开始可用。原始字符串字面量可以包含任意文本,而不需要转义序列。原始字符串文字可以包括空格和新行、嵌入引号和其他特殊字符。原始字符串文字包含在至少三个双引号(""")中:

"""
这是多行
        第二行缩进的字符串字面值。
"""

您甚至可以包含三个(或更多)双引号字符的序列。如果你的文本需要一个嵌入的引号序列,你可以根据需要用更多的引号开始和结束原始字符串文字:

"""""
这段原始 string 字面值有四个 """",数一数:"""" 四个!
在序列中嵌入引号字符。这就是为什么开头和结尾都有五个双引号。

您可以扩展此示例,根据文本需要使用任意多的嵌入引号。
"""""

原始 string 字面值通常在与嵌入文本分开的行上具有起始和结束引号序列。多行原始字符串字面量支持本身就是引号字符串的字符串:

string zfc =
      """
      "这是非常重要的信息。"
      """;
Console . WriteLine ( zfc ); // 输出 "这是非常重要的信息。"最左边的空格不是原始字符串字面值的一部分

当开始引号和结束引号位于不同的行时,开始引号之后和结束引号之前的换行符不包括在最终内容中。结束引号序列规定了 string 字面值的最左边的列。您可以缩进原始 string 文字以匹配整体代码格式。

保留结束引号序列右侧的列。该行为为JSON、YAML 或 XML 等数据格式启用原始字符串,如下例所示:

var json =
"""
{
"属性":0
}
""";
Console . WriteLine ( json );

上例输出:
{
"属性":0
}
如果任何文本行延伸到结束引号序列的左侧,编译器将发出错误。起始和结束引号序列可以在同一行,前提是 string 字面值既不以引号字符开始也不以引号字符结束:

var zfc短文本 = """在早上,你见了老师要说 "Good morning!"。""";

输出:
在早上,你见了老师要说 "Good morning!"。

您可以将原始 string 文字与 string 插值相结合,以在输出字符串中包含引号字符和大括号。

带引号的 string 字面值用双引号(")括起来:
"上帝保佑" // string 字面值

string 字面值可以包含任何字符字面值。转义序列包括在内。下面的示例使用转义序列“\”表示反斜杠,“\u0066”表示字母 f, “\n”表示 newline。

string a = "\\\u0066\n F";
Console . WriteLine ( a );
// 输出:
// \f
//  F

请注意:转义码“\dddd”(其中 dddd 是一个四位数)表示 Unicode 字符 U + dddd。也可以识别 8 位 Unicode 转义码:“\Udddddddd”。

逐字字符串以“@”开头,也用双引号括起来。例如:
@"good morning" // string 字面值

逐字字符串的优点是不处理转义序列,这使得它易于编写。例如,下面的文本匹配一个完全限定的 Windows 文件名:

@“C:\MyGames\List.html” // 等效于 “C:\\MyGames\\List.html”

要在“@”引号字符串中包含双引号,请使用两个双引号:
@"""啊!"" 小红终于想起答案了。" // "啊!" 小红终于想起答案了。

UTF-8 string 字面值

.NET 中的 string 使用 UTF-16 编码存储。UTF-8 是 Web 协议和其他重要库的标准。从 C# 11 开始,您可以向 string 字面值添加 u8 后缀以指定 UTF-8 编码。UTF-8 字面值存储为 ReadOnlySpan < byte > 对象。UTF-8 string 字面值的自然类型是 ReadOnlySpan < byte >。使用 UTF-8 string 文字比声明创建等效的 System . ReadOnlySpan < T > 更清晰的声明,如下代码所示:

ReadOnlySpan < byte > AuthWithTrailingSpace = new byte [ ] { 0x41, 0x55, 0x54, 0x48, 0x20 };
ReadOnlySpan < byte > AuthStringLiteral = "AUTH "u8;

上述两者等效,C# 会提示你下者更清晰。

将 UTF-8 string 字面值存储为数组需要使用 ReadOnlySpan < T > . ToArray ( ) 将包含该字面值的字节复制到可变数组中。

UTF-8 string 字面值不是编译时间常量;它们是运行时常数。因此,它们不能用作可选参数的默认值。UTF-8 string 字面值不能与 string 插值结合使用。不能在同一个 string 表达式上使用“$”标记和“u8”后缀。

delegate 委托类型

委托类型的声明类似于方法签名。它有一个返回值和任意数量的任意类型的形参:

public delegate void WT信息 ( string 信息 );
public delegate int WT( LX我的 m , long 数 );

在 .NET 中,System . Action 和 System . Func 类型为许多常见委托提供了泛型定义。您可能不需要定义新的自定义委托类型。相反,您可以创建所提供泛型类型的实例化。

委托是一种引用类型,可用于封装命名方法或匿名方法。委托类似于 C++ 中的函数指针;然而,委托是类型安全且可靠的。委托是事件的基础。可以通过将委托与命名方法或匿名方法关联来实例化委托。

必须使用具有兼容的返回类型和输入参数的方法或 lambda 表达式实例化委托。为了与匿名方法一起使用,委托和与之关联的代码被一起声明。

当运行时涉及的委托类型由于变量转换而不同时,委托组合或删除失败并出现运行时异常。下面的例子演示了一个失败的情况:

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// 由于 objectAction 到 Action < string > 的隐式引用转换而有效,但可能在运行时失败。
Action<string> combination = stringAction + objectAction;

可以通过创建新的 delegate 对象来创建具有正确运行时类型的委托。下面的示例演示如何将此解决方案应用于前面的示例。

Action < string > stringAction = str => { };
Action < object > objectAction = obj => { };
  
// 创建一个运行时类型为 Action < string > 的新委托实例
Action < string > wrappedObjectAction = new Action < string > ( objectAction );

// 两个 Action < string > 委托实例现在可以合并了
Action < string > combination = stringAction + wrappedObjectAction;

您可以声明函数指针,它们使用类似的语法。函数指针使用 calli 指令,而不是实例化委托类型并调用虚拟 Invoke 方法。

dynamic 动态类型

动态类型指示变量的使用和对其成员的引用绕过编译时类型检查。相反,这些操作在运行时解决。动态类型简化了对 COM api(如 Office Automation api)、对动态 api(如 IronPython 库)和 HTML 文档对象模型(DOM)的访问。

在大多数情况下,动态类型的行为类似于对象类型。特别是,任何非空表达式都可以转换为动态类型。动态类型与对象的不同之处在于,包含动态类型表达式的操作不会被编译器解析或检查类型。编译器将有关操作的信息打包在一起,这些信息稍后用于在运行时计算操作。作为这个过程的一部分,动态类型的变量被编译成对象类型的变量。因此,动态类型只存在于编译时,而不存在于运行时。

下面的示例将动态类型的变量与对象类型的变量进行对比。要在编译时验证每个变量的类型,请将鼠标指针放在 WriteLine 语句中的 DT 或 DX 上。将以下代码复制到 IntelliSense 可用的编辑器中。智能感知对 DT 显示动态,对 DX 显示对象。

dynamic DT = 1;
object DX = 1;
// 将鼠标指针停留在 DT 和 DX 上,在编译时查看它们的类型
Console . WriteLine ( DT . GetType ( ) );
Console . WriteLine ( DX . GetType ( ) );

WriteLine 语句显示 DT 和 DX 的运行时类型。在这一点上,两者具有相同的类型,整数。输出结果如下:
System.Int32
System.Int32
要查看 DT 和 DX 在编译时的区别,请在前面示例中的声明和 WriteLine 语句之间添加以下两行。

DT += 3;
DX += 3;

在表达式 DX += 3 中尝试添加一个整数和一个对象会报告编译器错误。然而,DT += 3 没有错误报告。包含 DT 的表达式在编译时不会被检查,因为 DT 的类型是动态的。

下面的例子在几个声明中使用了 dynamic。Main 方法还将编译时类型检查与运行时类型检查进行对比。

{
    class Program
    {
        static void Main ( string [ ] args )
            {
            ExampleClass ec = new ExampleClass ( );
            Console . WriteLine ( ec . ExampleMethod ( 10 ) );
            Console . WriteLine ( ec . ExampleMethod ( "值" ) );

            // 下面一行会导致编译器错误,因为 ExampleMethod 只接受一个参数
            // Console . WriteLine ( ec . ExampleMethod ( 10 , 4 ) );

            dynamic dynamic_ec = new ExampleClass ( );
            Console . WriteLine ( dynamic_ec . ExampleMethod ( 10 ) );

            // 因为 dynamic_ec 是动态的,所以下面用两个参数调用 ExampleMethod 不会在编译时产生错误
            // 但是,它确实会导致运行时错误
            // Console . WriteLine ( dynamic_ec . ExampleMethod ( 10 , 4 ) );

        }
    }

    class ExampleClass
    {
        static dynamic _field;
        dynamic Prop { get; set; }

        public dynamic ExampleMethod ( dynamic d )
        {
            dynamic local = "局部变量";
            int two = 2;

            if ( d is int )
            {
                return local;
            }
            else
            {
                return two;
            }
        }
    }
}
// 输出:
// 局部变量
// 2
// 局部变量

兔子码农
3 声望1 粉丝

一个酒晕子


« 上一篇
C# 的 default
下一篇 »
C# 的迭代语句