1 C#基础
1.1 简介
C#
是一个简单的、现代的、通用的、面向对象的编程语言,它是由微软(Microsoft)开发的。
C#
是专为公共语言基础结构(CLI
)设计的。CLI
由可执行代码
和运行时环境
组成,允许在不同的计算机平台和体系结构上使用各种高级语言。
C#
是 .Net
框架的一部分,且用于编写 .Net
应用程序。
.NET
平台由于其强大的功能和丰富的开发工具,成为了开发 Windows
应用程序的热门选择。无论是桌面应用、Web
应用还是服务应用,.NET
提供了一系列强大的框架和工具来帮助开发者高效地构建应用
.Net
框架应用程序是多平台的应用程序。框架的设计方式使它适用于语言:C#
、C++
、Visual Basic
、Jscript
、COBOL
等等。
.Net
框架由一个巨大的代码库组成,用于 C#
等客户端语言。下面列出一些 .Net 框架的组件:
- 公共语言运行库(
Common Language Runtime - CLR
) -
.Net
框架类库(.Net Framework Class Library
) - 公共语言规范(
Common Language Specification
) - 通用类型系统(
Common Type System
) - 元数据(
Metadata
)和组件(Assemblies
)
vWindows 窗体(Windows Forms
) -
ASP.Net
和ASP.Net AJAX
ADO.Net
-
Windows
工作流基础(Windows Workflow Foundation - WF
) -
Windows
显示基础(Windows Presentation Foundation
) -
Windows
通信基础(Windows Communication Foundation - WCF
) LINQ
.NET
的主要特性:
- 语言多样性:支持多种编程语言,包括 C#、VB.NET 和 F#。
- 跨平台:支持多种操作系统和设备。
- 强大的类库:提供了丰富的类库和 API 来简化开发工作。
- 集成开发环境:Visual Studio 提供了完整的开发工具集。
1.2 .Net历史
通过 .Net
历史可以知道 .NET Framework
、.NET Core
和.NET
关系
- 初始发布 (2002):
.NET Framework 1.0
发布,提供了基本的编程模型,包括ASP.NET
和Windows Forms
。 -
.NET Framework 1.1 (2003)
:引入了许多新特性,包括对移动设备的支持和新的数据访问技术(如ADO.NET
)。 -
.NET Framework 2.0 (2005)
:加入了泛型、ASP.NET 2.0、Windows Forms 2.0 等重要特性,提升了性能和开发效率。 -
.NET Framework 3.0 (2006)
:引入了 WCF(Windows Communication Foundation)、WPF(Windows Presentation Foundation)和 WF(Windows Workflow Foundation)。 -
.NET Framework 3.5 (2007)
:增加了 LINQ(语言集成查询)、ASP.NET AJAX 等功能。 -
.NET Framework 4.0 (2010)
:引入了并行编程、动态语言支持和更好的内存管理等特性。 -
.NET Framework 4.5 (2012)
:改进了异步编程模型,提供了新的 ASP.NET 和 WPF 功能。 -
.NET Core (2016)
:微软推出了跨平台的.NET Core
,支持在Windows
、Linux
和macOS
上运行,标志着 .NET 生态系统的重大转变。 -
.NET 5 (2020)
:合并了.NET Framework
和.NET Core
,成为统一的开发平台,简化了版本管理和开发流程。 -
.NET 6 (2021)
:作为长期支持 (LTS) 版本,进一步增强了性能和功能,支持更广泛的应用场景。 -
.NET 7 (2022)
:继续优化性能和开发体验,增加了对云原生开发的支持。
1.3 C#基本语法
1.3.1 C# 关键字
关键字是 C#
编译器预定义的保留字。这些关键字不能用作标识符,但是,如果想使用这些关键字作为标识符,可以在关键字前面加上 @
字符作为前缀。
在 C#
中,有些关键字在代码的上下文中有特殊的意义,如 get
和 set
,这些被称为上下文关键字(contextual keywords
)。
下表列出了 C# 中的保留关键字(Reserved Keywords)和上下文关键字(Contextual Keywords):
保留关键字 | ||||||
---|---|---|---|---|---|---|
abstract | as | base | bool | break | byte | case |
catch | char | checked | class | const | continue | decimal |
default | delegate | do | double | else | enum | event |
explicit | extern | false | finally | fixed | float | for |
foreach | goto | if | implicit | in | in (genericmodifier) | int |
interface | internal | is | lock | long | namespace | new |
null | object | operator | out | out(genericmodifier) | override | params |
private | protected | public | readonly | ref | return | sbyte |
sealed | short | sizeof | stackalloc | static | string | struct |
switch | this | throw | true | try | typeof | uint |
ulong | unchecked | unsafe | ushort | using | virtual | void |
volatile | while |
上下文关键字 | ||||||
---|---|---|---|---|---|---|
add | alias | ascending | descending | dynamic | from | get |
global | group | into | join | let | orderby | partial(type) |
partial(method) | remove | select | set |
1.3.2 顶级语句(Top-Level Statements)
在 C# 9.0
版本中,引入了顶级语句(Top-Level Statements
)的概念,这是一种新的编程范式,允许在文件的顶层直接编写语句,而不需要将它们封装在方法或类中。
特点:
- 无需类或方法:顶级语句允许你直接在文件的顶层编写代码,无需定义类或方法。
- 文件作为入口点:包含顶级语句的文件被视为程序的入口点,类似于 C# 之前的 Main 方法。
- 自动 Main 方法:编译器会自动生成一个 Main 方法,并将顶级语句作为 Main 方法的主体。
- 支持局部函数:尽管不需要定义类,但顶级语句的文件中仍然可以定义局部函数。
- 更好的可读性:对于简单的脚本或工具,顶级语句提供了更好的可读性和简洁性。
- 适用于小型项目:顶级语句非常适合小型项目或脚本,可以快速编写和运行代码。
- 与现有代码兼容:顶级语句可以与现有的 C# 代码库一起使用,不会影响现有代码。
传统 C# 代码 - 在使用顶级语句之前,必须像这样编写一个 C# 程序:
using System;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
使用顶级语句的 C#
代码 - 使用顶级语句,可以简化为:
using System;
Console.WriteLine("Hello, World!");
顶级语句支持所有常见的 C#
语法,包括声明变量、定义方法、处理异常等。
using System;
using System.Linq;
// 顶级语句中的变量声明
int number = 42;
string message = "The answer to life, the universe, and everything is";
// 输出变量
Console.WriteLine($"{message} {number}.");
// 定义和调用方法
int Add(int a, int b) => a + b;
Console.WriteLine($"Sum of 1 and 2 is {Add(1, 2)}.");
// 使用 LINQ
var numbers = new[] { 1, 2, 3, 4, 5 };
var evens = numbers.Where(n => n % 2 == 0).ToArray();
Console.WriteLine("Even numbers: " + string.Join(", ", evens));
// 异常处理
try
{
int zero = 0;
int result = number / zero;
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Error: " + ex.Message);
}
注意事项
:
- 文件限制:顶级语句只能在一个源文件中使用。如果在一个项目中有多个使用顶级语句的文件,会导致编译错误。
- 程序入口:如果使用顶级语句,则该文件会隐式地包含 Main 方法,并且该文件将成为程序的入口点。
- 作用域限制:顶级语句中的代码共享一个全局作用域,这意味着可以在顶级语句中定义的变量和方法可以在整个文件中访问。
1.4 类型转换
在 C#
中,类型转换是将一个数据类型的值转换为另一个数据类型的过程。可以分为两种:隐式类型转换和显式类型转换
1.4.1 隐式类型转换
隐式转换是不需要编写代码来指定的转换,编译器会自动进行。
隐式转换是指将一个较小范围的数据类型转换为较大范围的数据类型时,编译器会自动完成类型转换,这些转换是 默认的以安全方式进行的转换, 不会导致数据丢失。
例如,从 int 到 long,从 float 到 double 等。
从小的整数类型转换为大的整数类型,从派生类转换为基类。将一个 byte
类型的变量赋值给 int
类型的变量,编译器会自动将 byte 类型转换为 int 类型,不需要显示转换。
byte b = 10;
int i = b; // 隐式转换,不需要显式转换
将一个整数赋值给一个长整数,或者将一个浮点数赋值给一个双精度浮点数,这种转换不会导致数据丢失:
int intValue = 42;
long longValue = intValue; // 隐式转换,从 int 到 long
1.4.2 显式转换
1.4.2.1 讲解
显式类型转换,即强制类型转换,需要程序员在代码中明确指定,指将一个较大范围的数据类型转换为较小范围的数据类型时,或者将一个对象类型转换为另一个对象类型时,需要使用强制类型转换符号进行显示转换,强制转换会造成数据丢失。
例如,将一个 int 类型的变量赋值给 byte 类型的变量,需要显示转换。
int i = 10;
byte b = (byte)i; // 显式转换,需要使用强制类型转换符号
强制转换为整数类型:
double doubleValue = 3.14;
int intValue = (int)doubleValue; // 强制从 double 到 int,数据可能损失小数部分
1.4.2.2 is/as/强制
- is 操作符
is
操作符用于检查一个对象是否是某个类型的实例,它的返回值是一个布尔值(true 或 false
)。如果对象是指定类型的实例,is 返回 true,否则返回 false。
特点:- is 操作符不会进行强制转换,而是检查对象是否可以被转换为指定类型。
- 使用 is 可以避免 InvalidCastException 异常,不会抛出异常,直接判断类型。
- is 适用于类型检查和条件判断。
- 可用于类型模式(
Pattern Matching
),C# 7.0
引入了类型模式,使得 is 操作符可以直接在条件语句中进行赋值
- as 操作符
as
操作符尝试将对象转换为指定类型。如果转换成功,返回转换后的对象;如果失败,返回null
。as
适用于引用类型和可空类型的转换,不适用于值类型
(除非是可空值类型Nullable<T>
)。
特点:- as 只适用于
引用类型
(包括接口、类、数组等)和可空值类型(如Nullable<int>
)。 - 如果对象不能转换为指定的类型,
as 返回 null
。 - as 不会抛出异常(与强制类型转换不同),而是返回 null,因此需要在使用时检查 null
- as 只适用于
- 强制类型转换
()
强制类型转换是直接将一个对象转换为目标类型。如果对象的实际类型不能转换为目标类型,强制转换会抛出InvalidCastException
异常。
特点:- 转换类型:
()
直接将对象转换为目标类型。 - 可能抛出异常:如果对象不能转换为指定类型,会抛出
InvalidCastException
异常。 - 适用于
引用类型和值类型
:可以用于引用类型、值类型以及值类型和引用类型之间的转换。
- 转换类型:
object obj = "Hello, World!";
if (obj is string str) // 试图将 obj 转换为 string
{
Console.WriteLine(str); // 输出: Hello, World!
}
object obj = "Hello, World!";
string str = obj as string; // 尝试将 obj 转换为 string
if (str != null)
{
Console.WriteLine(str); // 输出: Hello, World!
}
else
{
Console.WriteLine("转换失败");
}
区别总结
特性 | is | as | 强制类型转换 () |
---|---|---|---|
功能 | 检查对象是否是指定类型的实例 | 尝试将对象转换为指定类型,失败返回 null | 强制将对象转换为指定类型,失败抛出异常 |
返回值 | true 或 false,用于类型检查 | 转换后的类型实例,或者 null | 转换后的目标类型实例,失败抛出异常 |
安全性 | 非常安全,不会抛出异常 | 安全,不会抛出异常,失败返回 null | 不安全,失败抛出 InvalidCastException 异常 |
适用类型 | 适用于所有类型(值类型、引用类型) | 只适用于引用类型和可空类型 | 适用于所有类型(值类型、引用类型) |
用法 | 用于类型检查和类型模式匹配 | 用于尝试转换并处理失败的情况 | 用于直接转换,适合已知类型的转换 |
1.4.3 类型转换方法
C#
提供了多种类型转换方法,例如使用 Convert
类、Parse
方法和 TryParse
方法,这些方法可以帮助处理不同的数据类型之间的转换。
1.4.3.1 Convert 类
Convert
类提供了一组静态方法,可以在各种基本数据类型之间进行转换。
string str = "123";
int num = Convert.ToInt32(str);
1.4.3.2 Parse 方法
Parse
方法用于将字符串转换为对应的数值类型,如果转换失败会抛出异常。
string str = "123.45";
double d = double.Parse(str);
1.4.3.3 TryParse 方法
TryParse
方法类似于 Parse
,但它不会抛出异常,而是返回一个布尔值指示转换是否成功。
string str = "123.45";
double d;
bool success = double.TryParse(str, out d);
if (success) {
Console.WriteLine("转换成功: " + d);
} else {
Console.WriteLine("转换失败");
}
1.4.3.4 自定义类型转换
自定义类型转换操作,通过在类型中定义 implicit
或 explicit
关键字
using System;
public class Fahrenheit
{
public double Degrees { get; set; }
public Fahrenheit(double degrees)
{
Degrees = degrees;
}
// 隐式转换从Fahrenheit到double
public static implicit operator double(Fahrenheit f)
{
return f.Degrees;
}
// 显式转换从double到Fahrenheit
public static explicit operator Fahrenheit(double d)
{
return new Fahrenheit(d);
}
}
public class Program
{
public static void Main()
{
Fahrenheit f = new Fahrenheit(98.6);
Console.WriteLine("Fahrenheit object: " + f.Degrees + " degrees");
double temp = f; // 隐式转换
Console.WriteLine("After implicit conversion to double: " + temp + " degrees");
Fahrenheit newF = (Fahrenheit)temp; // 显式转换
Console.WriteLine("After explicit conversion back to Fahrenheit: " + newF.Degrees + " degrees");
}
}
输出结果将显示如下:
Fahrenheit object: 98.6 degrees
After implicit conversion to double: 98.6 degrees
After explicit conversion back to Fahrenheit: 98.6 degrees
以上例子中,我们定义了一个 Fahrenheit 类,并实现了从 Fahrenheit 到 double 的隐式转换和从 double 到 Fahrenheit 的显式转换。
1.4.3.5 类型转换方法汇总
以下是 C# 内置类型转换方法的表格:
方法类别 | 方法 | 描述 | |
---|---|---|---|
Convert 类方法 | Convert.ToBoolean(value) | 将指定类型转换为 Boolean | |
Convert.ToByte(value) | 将指定类型转换为 Byte | ||
Convert.ToChar(value) | 将指定类型转换为 Char | ||
Convert.ToDateTime(value) | 将指定类型转换为 DateTime | ||
Convert.ToDecimal(value) | 将指定类型转换为 Decimal | ||
Convert.ToDouble(value) | 将指定类型转换为 Double | ||
Convert.ToInt16(value) | 将指定类型转换为 Int16(短整型) | ||
Convert.ToInt32(value) | 将指定类型转换为 Int32(整型) | ||
Convert.ToInt64(value) | 将指定类型转换为 Int64(长整型) | ||
Convert.ToSByte(value) | 将指定类型转换为 SByte | ||
Convert.ToSingle(value) | 将指定类型转换为 Single(单精度浮点型) | ||
Convert.ToString(value) | 将指定类型转换为 String | ||
Convert.ToUInt16(value) | 将指定类型转换为 UInt16(无符号短整型) | ||
Convert.ToUInt32(value) | 将指定类型转换为 UInt32(无符号整型) | ||
Convert.ToUInt64(value) | 将指定类型转换为 UInt64(无符号长整型) | ||
Parse 方法 | Boolean.Parse(string) | 将字符串解析为 Boolean | |
Byte.Parse(string) | 将字符串解析为 Byte | ||
Char.Parse(string) | 将字符串解析为 Char | ||
DateTime.Parse(string) | 将字符串解析为 DateTime | ||
Decimal.Parse(string) | 将字符串解析为 Decimal | ||
Double.Parse(string) | 将字符串解析为 Double | ||
Int16.Parse(string) | 将字符串解析为 Int16 | ||
Int32.Parse(string) | 将字符串解析为 Int32 | ||
Int64.Parse(string) | 将字符串解析为 Int64 | ||
SByte.Parse(string) | 将字符串解析为 SByte | ||
Single.Parse(string) | 将字符串解析为 Single | ||
UInt16.Parse(string) | 将字符串解析为 UInt16 | ||
UInt32.Parse(string) | 将字符串解析为 UInt32 | ||
UInt64.Parse(string) | 将字符串解析为 UInt64 | ||
TryParse 方法 | Boolean.TryParse(string, out bool) | 尝试将字符串解析为 Boolean,返回布尔值表示是否成功 | |
Byte.TryParse(string, out byte) | 尝试将字符串解析为 Byte,返回布尔值表示是否成功 | ||
Char.TryParse(string, out char) | 尝试将字符串解析为 Char,返回布尔值表示是否成功 | ||
DateTime.TryParse(string, out DateTime) | 尝试将字符串解析为 DateTime,返回布尔值表示是否成功 | ||
Decimal.TryParse(string, out decimal) | 尝试将字符串解析为 Decimal,返回布尔值表示是否成功 | ||
Double.TryParse(string, out double) | 尝试将字符串解析为 Double,返回布尔值表示是否成功 | ||
Int16.TryParse(string, out short) | 尝试将字符串解析为 Int16,返回布尔值表示是否成功 | ||
Int32.TryParse(string, out int) | 尝试将字符串解析为 Int32,返回布尔值表示是否成功 | ||
Int64.TryParse(string, out long) | 尝试将字符串解析为 Int64,返回布尔值表示是否成功 | ||
SByte.TryParse(string, out sbyte) | 尝试将字符串解析为 SByte,返回布尔值表示是否成功 | ||
Single.TryParse(string, out float) | 尝试将字符串解析为 Single,返回布尔值表示是否成功 | ||
UInt16.TryParse(string, out ushort) | 尝试将字符串解析为 UInt16,返回布尔值表示是否成功 | ||
UInt32.TryParse(string, out uint) | 尝试将字符串解析为 UInt32,返回布尔值表示是否成功 | ||
UInt64.TryParse(string, out ulong) | 尝试将字符串解析为 UInt64,返回布尔值表示是否成功 |
在进行类型转换时需要注意以下几点:
- 隐式转换只能将较小范围的数据类型转换为较大范围的数据类型,不能将较大范围的数据类型转换为较小范围的数据类型;
- 显式转换可能会导致数据丢失或精度降低,需要进行数据类型的兼容性检查;
- 对于对象类型的转换,需要进行类型转换的兼容性检查和类型转换的安全性检查。
1.5 运算符
运算符是一种告诉编译器执行特定的数学或逻辑操作的符号
1.5.1 算术运算符
下表显示了 C# 支持的所有算术运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
-- | 自减运算符,整数值减少 1 | A-- 将得到 9 |
1.5.2 关系运算符
下表显示了 C# 支持的所有关系运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真 | (A >= B) 不为真 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真 | (A <= B) 为真 |
1.5.3 逻辑运算符
下表显示了 C# 支持的所有逻辑运算符。假设变量 A 为布尔值 true,变量 B 为布尔值 false,则:
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真 | (A && B) 为假 |
| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真 | (A || B) 为真 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假 | !(A && B) 为真 |
1.5.4 位运算符
假设变量 A 的值为 60,变量 B 的值为 13,则:
运算符 | 描述 | 实例 |
---|---|---|
& | 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中 | (A & B) 将得到 12,即为 0000 1100 |
如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中 | (A| B) 将得到 61,即为 0011 1101 | |
^ | 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中 | (A ^ B) 将得到 49,即为 0011 0001 |
~ | 按位取反运算符是一元运算符,具有"翻转"位效果,即0变成1,1变成0,包括符号位 | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式 |
<< | 二进制左移运算符。左操作数的值向左移动右操作数指定的位数 | A << 2 将得到 240,即为 1111 0000 |
>> | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数 | A >> 2 将得到 15,即为 0000 1111 |
1.5.5 赋值运算符
下表列出了 C# 支持的赋值运算符:
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
1.5.6 其他运算符
下表列出了 C# 支持的其他一些重要的运算符,包括 sizeof、typeof 和 ? :
运算符 | 描述 | 实例 |
---|---|---|
sizeof() | 返回数据类型的大小 | sizeof(int),将返回 4. |
typeof() | 返回 class 的类型 | typeof(StreamReader); |
& | 返回变量的地址 | &a; 将得到变量的实际地址。 |
* | 变量的指针 | *a; 将指向一个变量。 |
? : | 条件表达式 | 如果条件为真 ? 则为 X : 否则为 Y |
is | 判断对象是否为某一类型 | If( Ford is Car) 检查 Ford 是否是 Car 类的一个对象 |
as | 强制转换,与直接用小括号强转相比,即使转换失败也不会抛出异常 | Object obj = new StringReader("Hello"); StringReader r = obj as StringReader; |
1.5.7 运算符优先级
运算符的优先级确定表达式中项的组合。这会影响到一个表达式如何计算
下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。
优先级简易概括
:有括号先括号,后乘除在加减,然后位移再关系,逻辑完后条件,最后一个逗号 ,
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
1.6 可空类型(Nullable)
1.6.1 简介
C# 单问号 ?
与 双问号 ??
区别:
?
单问号用于对 int、double、bool
等无法直接赋值为 null
的数据类型进行 null
的赋值,意思是这个数据类型是 Nullable 类型的。
int? i = 3;
等同于:
Nullable<int> i = new Nullable<int>(3);
int i; //默认值0
int? ii; //默认值null
??
双问号用于判断一个变量在为 null
的时候返回一个指定的值
。
1.6.2 示例
1.6.2.1 可空类型
C# 提供了一个特殊的数据类型,nullable
类型(可空类型),可空类型可以表示其基础值类型正常范围内的值,再加上一个 null 值。
例如,Nullable< Int32 >
,读作"可空的 Int32",可以被赋值为 -2,147,483,648 到 2,147,483,647 之间的任意值,也可以被赋值为 null
值。类似的,Nullable<bool>
变量可以被赋值为 true 或 false 或 null。
在处理数据库和其他包含可能未赋值的元素的数据类型时,将 null 赋值给数值类型或布尔型的功能特别有用。例如,数据库中的布尔型字段可以存储值 true 或 false,或者,该字段也可以未定义。
声明一个 nullable 类型(可空类型)的语法如下:
< data_type> ? <variable_name> = null;
下面的实例演示了可空数据类型的用法:
using System;
namespace CalculatorApplication
{
class NullablesAtShow
{
static void Main(string[] args)
{
int? num1 = null;
int? num2 = 45;
double? num3 = new double?();
double? num4 = 3.14157;
bool? boolval = new bool?();
// 显示值
Console.WriteLine("显示可空类型的值: {0}, {1}, {2}, {3}",
num1, num2, num3, num4);
Console.WriteLine("一个可空的布尔值: {0}", boolval);
Console.ReadLine();
}
}
}
1.6.2.2 合并运算符
Null
合并运算符用于定义可空类型和引用类型的默认值。Null
合并运算符为类型转换定义了一个预设值,以防可空类型的值为 Null
。Null
合并运算符把操作数类型隐式转换为另一个可空(或不可空)的值类型的操作数的类型。
如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。下面的实例演示了这点:
using System;
namespace CalculatorApplication
{
class NullablesAtShow
{
static void Main(string[] args)
{
double? num1 = null;
double? num2 = 3.14157;
double num3;
num3 = num1 ?? 5.34; // num1 如果为空值则返回 5.34
Console.WriteLine("num3 的值: {0}", num3);
num3 = num2 ?? 5.34;
Console.WriteLine("num3 的值: {0}", num3);
Console.ReadLine();
}
}
}
结果:
num3 的值: 5.34
num3 的值: 3.14157