ZetCode--NET-教程-一-
ZetCode .NET 教程(一)
原文:ZetCode
Visual Basic 中的集合
在本章中,我们将处理 Visual Basic 集合。 .NET 框架为数据存储和检索提供了专门的类。 在上一章中,我们描述了数组。 集合是对数组的增强。
在 Visual Basic 中,有两种不同的集合类型。 标准集合位于System.Collections
命名空间下,而泛型集合位于System.Collections.Generic
下。 泛型集合更灵活,是处理数据的首选方式。 .NET Framework 2.0 中引入了泛型集合或泛型。 泛型可增强代码重用性,类型安全性和性能。
泛型编程是一种计算机编程样式,其中,算法根据待指定的后来的类型编写,然后在需要作为参数提供的特定类型时实例化。 这种方法由 Ada 于 1983 年率先提出,它允许编写仅在使用时所使用的类型集不同的常见功能或类型,从而减少了重复。 (维基百科)
ArrayList
ArrayList
是来自标准System.Collections
命名空间的集合。 它是一个动态数组。 它提供对元素的随机访问。 ArrayList
随着添加数据而自动扩展。 与数组不同,ArrayList
可以保存多种数据类型的数据。 ArrayList
中的元素通过整数索引访问。 索引从零开始。 元素的索引以及在ArrayList
末尾的插入和删除需要固定的时间。 在动态数组的中间插入或删除元素的成本更高。 这需要线性时间。
Option Strict On
Imports System.Collections
Module Example
Class Empty
End Class
Sub Main()
Dim da As ArrayList = New ArrayList()
da.Add("Visual Basic")
da.Add(344)
da.Add(55)
da.Add(New Empty)
da.Remove(55)
For Each el As Object In da
Console.WriteLine(el)
Next
End Sub
End Module
在上面的示例中,我们创建了一个ArrayList
集合。 我们添加了一些元素。 它们具有各种数据类型:一个字符串,两个整数和一个类对象。
Imports System.Collections
为了使用ArrayList
集合,我们需要导入System.Collections
命名空间。
Dim da As ArrayList = New ArrayList()
ArrayList
集合已创建。
da.Add("Visual Basic")
da.Add(344)
da.Add(55)
da.Add(New Empty)
我们使用Add()
方法将五个元素添加到数组中。
da.Remove(55)
我们删除一个元素。
For Each el As Object In da
Console.WriteLine(el)
Next
我们遍历数组并将其元素打印到控制台。
List
List
是可以通过索引访问的对象的强类型列表。 可以在System.Collections.Generic
命名空间下找到。
Option Strict On
Imports System.Collections.Generic
Module Example
Sub Main()
Dim langs As New List(Of String)
langs.Add("Java")
langs.Add("C#")
langs.Add("C")
langs.Add("C++")
langs.Add("Ruby")
langs.Add("Javascript")
Console.WriteLine(langs.Contains("C#"))
Console.WriteLine(langs(1))
Console.WriteLine(langs(2))
langs.Remove("C#")
langs.Remove("C")
Console.WriteLine(langs.Contains("C#"))
langs.Insert(4, "Haskell")
langs.Sort()
For Each lang As String In langs
Console.WriteLine(lang)
Next
End Sub
End Module
在前面的示例中,我们使用List
集合。
Imports System.Collections.Generic
为了使用List
集合,我们需要导入System.Collections.Generic
命名空间。
Dim langs As New List(Of String)
将创建一个通用动态数组。 我们指定将使用带有Of
关键字的字符串。
langs.Add("Java")
langs.Add("C#")
langs.Add("C")
...
我们使用Add()
方法将元素添加到列表中。
Console.WriteLine(langs.Contains("C#"))
我们使用Contains()
方法检查列表是否包含特定的字符串。
Console.WriteLine(langs(1))
Console.WriteLine(langs(2))
我们使用索引符号访问List
的第二个和第三个元素。
langs.Remove("C#")
langs.Remove("C")
我们从列表中删除两个字符串。
langs.Insert(4, "Haskell")
我们在特定位置插入一个字符串。
langs.Sort()
我们使用Sort()
方法对元素进行排序。
$ ./list.exe
True
C#
C
False
C++
Haskell
Java
Javascript
Ruby
示例的结果。
LinkedList
LinkedList
是 Visual Basic 中的通用双链表。 LinkedList
仅允许顺序访问。 LinkedList
允许进行固定时间的插入或删除,但只允许顺序访问元素。 由于链表需要额外的存储空间以供参考,因此对于诸如字符之类的小型数据项列表来说,它们是不切实际的。 与动态数组不同,可以将任意数量的项目添加到链表(当然受内存限制)而无需重新分配,这是一项昂贵的操作。
Option Strict On
Imports System.Collections.Generic
Module Example
Sub Main()
Dim nums As New LinkedList(Of Integer)
nums.AddLast(23)
nums.AddLast(34)
nums.AddLast(33)
nums.AddLast(11)
nums.AddLast(6)
nums.AddFirst(9)
nums.AddFirst(7)
Dim node as LinkedListNode(Of Integer)
node = nums.Find(6)
nums.AddBefore(node, 5)
For Each num As Integer In nums
Console.WriteLine(num)
Next
End Sub
End Module
这是一个LinkedList
示例,其中包含一些方法。
Dim nums As New LinkedList(Of Integer)
这是一个整数LinkedList
。
nums.AddLast(23)
...
nums.AddFirst(9)
我们使用AddLast()
和AddFirst()
方法填充链表。
Dim node as LinkedListNode(Of Integer)
node = nums.Find(6)
nums.AddBefore(node, 5)
LinkedList
由节点组成。 我们找到一个特定的节点,并在其之前添加一个元素。
For Each num As Integer In nums
Console.WriteLine(num)
Next
将所有元素打印到控制台。
Dictionary
dictionary
,也称为关联数组,是唯一键和值的集合,其中每个键与一个值相关联。 检索和添加值非常快。 字典占用更多内存,因为每个值都有一个键。
Option Strict On
Imports System.Collections.Generic
Module Example
Sub Main()
Dim domains As New Dictionary(Of String, String)
domains.Add("de", "Germany")
domains.Add("sk", "Slovakia")
domains.Add("us", "United States")
domains.Add("ru", "Russia")
domains.Add("hu", "Hungary")
domains.Add("pl", "Poland")
Console.WriteLine(domains("sk"))
Console.WriteLine(domains("de"))
Console.WriteLine("Dictionary has {0} items", _
domains.Count)
Console.WriteLine("Keys of the dictionary:")
Dim keys As Dictionary(Of String, String).KeyCollection = domains.Keys
For Each key As String In keys
Console.WriteLine("{0}", key)
Next
Console.WriteLine("Values of the dictionary:")
Dim vals As Dictionary(Of String, String).ValueCollection = domains.Values
For Each val As String In vals
Console.WriteLine("{0}", val)
Next
Console.WriteLine("Keys and values of the dictionary:")
For Each kvp As KeyValuePair(Of String, String) In domains
Console.WriteLine("Key = {0}, Value = {1}", _
kvp.Key, kvp.Value)
Next
End Sub
End Module
我们有一本字典,我们在其中将域名映射到其国家名称。
Dim domains As New Dictionary(Of String, String)
我们创建一个包含字符串键和值的字典。
domains.Add("de", "Germany")
domains.Add("sk", "Slovakia")
domains.Add("us", "United States")
...
我们将一些数据添加到字典中。 第一个字符串是键。 第二是值。
Console.WriteLine(domains("sk"))
Console.WriteLine(domains("de"))
在这里,我们通过它们的键检索两个值。
Console.WriteLine("Dictionary has {0} items", _
domains.Count)
我们通过引用Count
属性来打印项目数。
Dim keys As Dictionary(Of String, String).KeyCollection = domains.Keys
For Each key As String In keys
Console.WriteLine("{0}", key)
Next
这些行从字典中检索所有键。
Dim vals As Dictionary(Of String, String).ValueCollection = domains.Values
For Each val As String In vals
Console.WriteLine("{0}", val)
Next
这些行从字典中检索所有值。
For Each kvp As KeyValuePair(Of String, String) In domains
Console.WriteLine("Key = {0}, Value = {1}", _
kvp.Key, kvp.Value)
Next
最后,我们同时打印字典的键和值。
./dictionary.exe
Slovakia
Germany
Dictionary has 6 items
Keys of the dictionary:
de
sk
us
ru
hu
pl
Values of the dictionary:
Germany
Slovakia
United States
Russia
Hungary
Poland
Keys and values of the dictionary:
Key = de, Value = Germany
Key = sk, Value = Slovakia
Key = us, Value = United States
Key = ru, Value = Russia
Key = hu, Value = Hungary
Key = pl, Value = Poland
这是示例的输出。
queue
queue
是先进先出(FIFO)数据结构。 添加到队列中的第一个元素将是第一个要删除的元素。 队列可以用于处理消息出现时的消息,也可以用于消息到达时为客户服务的消息。 首先服务的是第一个客户。
Option Strict On
Imports System.Collections.Generic
Module Example
Sub Main()
Dim msgs As New Queue(Of String)
msgs.Enqueue("Message 1")
msgs.Enqueue("Message 2")
msgs.Enqueue("Message 3")
msgs.Enqueue("Message 4")
msgs.Enqueue("Message 5")
Console.WriteLine(msgs.Dequeue())
Console.WriteLine(msgs.Peek())
Console.WriteLine(msgs.Peek())
Console.WriteLine()
For Each msg As String In msgs
Console.WriteLine(msg)
Next
End Sub
End Module
在我们的示例中,我们有一个包含消息的队列。
Dim msgs As New Queue(Of String)
创建字符串队列。
msgs.Enqueue("Message 1")
msgs.Enqueue("Message 2")
...
Enqueue()
将消息添加到队列末尾。
Console.WriteLine(msgs.Dequeue())
Dequeue()
方法删除并返回队列开头的项目。
Console.WriteLine(msgs.Peek())
Peek()
方法从队列中返回下一项,但不会将其从集合中删除。
$ ./queue.exe
Message 1
Message 2
Message 2
Message 2
Message 3
Message 4
Message 5
Dequeue()
方法从集合中删除Message 1
。 Peek()
方法没有。 Message 2
保留在集合中。
stack
stack
是后进先出(LIFO)数据结构。 添加到队列中的最后一个元素将是第一个要删除的元素。 C 语言使用栈将本地数据存储在函数中。 实现计算器时,还将使用该栈。
Option Strict On
Imports System.Collections.Generic
Module Example
Sub Main()
Dim stc As New Stack(Of Integer)
stc.Push(1)
stc.Push(4)
stc.Push(3)
stc.Push(6)
stc.Push(4)
Console.WriteLine(stc.Pop())
Console.WriteLine(stc.Peek())
Console.WriteLine(stc.Peek())
Console.WriteLine()
For Each item As Integer In stc
Console.WriteLine(item)
Next
End Sub
End Module
上面有一个简单的栈示例。
Dim stc As New Stack(Of Integer)
创建一个Stack
数据结构。
stc.Push(1)
stc.Push(4)
...
Push()
方法将一个项目添加到栈的顶部。
Console.WriteLine(stc.Pop())
Pop()
方法从栈顶部删除并返回该项目。
Console.WriteLine(stc.Peek())
Peek()
方法从栈顶部返回该项目。 它不会删除它。
$ ./stack.exe
4
6
6
6
3
4
1
输出。
Visual Basic 教程的这一部分专用于 Visual Basic 中的集合。
输入和输出
本章专门介绍 Visual Basic 中的输入和输出。 Visual Basic 中的输入和输出基于流。
流是与输入和输出一起使用的对象。 流是字节序列的抽象,例如文件,输入/输出设备,进程间通信管道或 TCP/IP 套接字。 在 Visual Basic 中,我们有一个Stream
类,它是所有流的抽象类。 还有一些其他类可以从Stream
类派生而来,从而使编程更加容易。
MemoryStream
MemoryStream
是用于处理计算机内存中数据的流。
Option Strict On
Imports System.IO
Module Example
Sub Main()
Dim ms As Stream = New MemoryStream(6)
ms.WriteByte(9)
ms.WriteByte(11)
ms.WriteByte(6)
ms.WriteByte(8)
ms.WriteByte(3)
ms.WriteByte(7)
ms.Position = 0
Dim rs As Integer
rs = ms.ReadByte()
Do While rs <> -1
Console.WriteLine(rs)
rs = ms.ReadByte()
Loop
ms.Close()
End Sub
End Module
我们用MemoryStream
将六个数字写入存储器。 然后,我们读取这些数字并将其打印到控制台。
Dim ms As Stream = New MemoryStream(6)
该行创建并初始化一个容量为六个字节的MemoryStream
对象。
ms.Position = 0
我们使用Position
属性将光标在流中的位置设置为开头。
ms.WriteByte(9)
ms.WriteByte(11)
ms.WriteByte(6)
...
WriteByte()
方法在当前位置的当前流中写入一个字节。
Do While rs <> -1
Console.WriteLine(rs)
rs = ms.ReadByte()
Loop
在这里,我们从流中读取所有字节并将其打印到控制台。
ms.Close()
最后,我们关闭流。
$ ./memory.exe
9
11
6
8
3
7
示例的输出。
StreamReader
& StreamWriter
StreamReader
从字节流中读取字符。 默认为 UTF-8 编码。 StreamWriter
以特定编码将字符写入流。
Option Strict On
Imports System.IO
Module Example
Sub Main()
Dim file As String
file = "languages"
Try
Dim stream As StreamReader
stream = New StreamReader(file)
Console.WriteLine(stream.ReadToEnd())
Catch e As IOException
Console.WriteLine("Cannot read file.")
End Try
End Sub
End Module
我们有一个名为Languages
的文件。 我们从该文件中读取字符并将其打印到控制台。
Dim stream As StreamReader
stream = New StreamReader(file)
StreamReader
以文件名作为参数。
Console.WriteLine(stream.ReadToEnd())
ReadToEnd()
方法读取所有字符到流的末尾。
$ cat languages
Python
Visual Basic
PERL
Java
C
C#
$ ./readfile.exe
Python
Visual Basic
PERL
Java
C
C#
当前目录中有一个语言文件。 我们将文件的所有行打印到控制台。
在下一个示例中,我们将对行进行计数。
Option Strict On
Imports System.IO
Module Example
Sub Main()
Dim file As String
Dim count As Integer
file = "languages"
Try
Dim stream As StreamReader
stream = New StreamReader(file)
While Not (stream.ReadLine() Is Nothing)
count += 1
End While
Catch e As IOException
Console.WriteLine("Cannot read file.")
End Try
Console.WriteLine("There are {0} lines", count)
End Sub
End Module
计算文件中的行数。
While Not (stream.ReadLine() Is Nothing)
count += 1
End While
在While
循环中,我们使用ReadLine()
方法从流中读取一行。 它从流返回一行,如果到达输入流的末尾,则返回Nothing
。
以下是StreamWriter
的示例。
Option Strict On
Imports System.IO
Module Example
Sub Main()
Dim mstream As New MemoryStream()
Dim swriter As New StreamWriter(mstream)
swriter.Write("ZetCode, tutorials for programmers.")
swriter.Flush()
Dim sreader As New StreamReader(mstream)
Console.WriteLine(sreader.ReadToEnd())
sreader.Close()
End Sub
End Module
在前面的示例中,我们将字符写入内存。
Dim mstream As New MemoryStream()
创建了MemoryStream
。
Dim swriter As New StreamWriter(mstream)
StreamWriter
类将内存流作为参数。 这样,我们将要写入内存流。
swriter.Write("ZetCode, tutorials for programmers.")
swriter.Flush()
我们写一些文本和写入器。 Flush()
清除当前写入器的所有缓冲区,并使所有缓冲的数据写入基础流。
Dim sreader As New StreamReader(mstream)
Console.WriteLine(sreader.ReadToEnd())
现在,我们创建流读取器的实例,并读取回写的所有内容。
FileStream
FileStream
类在文件系统上的文件上使用流。 此类可用于读取文件,写入文件,打开文件和关闭文件。
Option Strict On
Imports System.IO
Imports System.Text
Module Example
Sub Main()
Dim fstream As New FileStream("author", FileMode.Append)
Dim bytes As Byte() = New UTF8Encoding().GetBytes("Фёдор Михайлович Достоевский")
fstream.Write(bytes, 0, bytes.Length)
fstream.Close()
End Sub
End Module
我们用俄语西里尔字母将一些文本写入当前工作目录中的文件。
Dim fstream As New FileStream("author", FileMode.Append)
创建一个FileStream
对象。 第二个参数是打开文件的模式。 附加模式将打开文件(如果存在)并查找到文件末尾,或创建一个新文件。
Dim bytes As Byte() = New UTF8Encoding().GetBytes("Фёдор Михайлович Достоевский")
我们使用俄语西里尔字母中的文本创建字节数组。
fstream.Write(bytes, 0, bytes.Length)
我们将字节写入文件流。
XmlTextReader
我们可以使用流来读取 XML 数据。 XmlTextReader
是在 Visual Basic 中读取 XML 文件的类。 该类是仅转发和只读的。
我们有以下 XML 测试文件。
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language>Python</language>
<language>Ruby</language>
<language>Javascript</language>
<language>C#</language>
</languages>
Option Strict On
Imports System.IO
Imports System.Xml
Module Example
Sub Main()
Dim file As String
file = "languages.xml"
Try
Dim xreader As New XmlTextReader(file)
xreader.MoveToContent()
Do While xreader.Read()
Select Case xreader.NodeType
Case XmlNodeType.Element
Console.Write(xreader.Name & ": ")
Case XmlNodeType.Text
Console.WriteLine(xreader.Value)
End Select
Loop
xreader.Close()
Catch e As IOException
Console.WriteLine("Cannot read file.")
Catch e As XmlException
Console.WriteLine("XML parse error")
End Try
End Sub
End Module
此 Visual Basic 程序从先前指定的 XML 文件中读取数据并将其打印到终端。
Dim xreader As New XmlTextReader(file)
创建一个XmlTextReader
对象。 它以文件名作为参数。
xreader.MoveToContent()
MoveToContent()
方法移至 XML 文件的实际内容。
Do While xreader.Read()
该行从流中读取下一个节点。
Case XmlNodeType.Element
Console.Write(xreader.Name & ": ")
Case XmlNodeType.Text
Console.WriteLine(xreader.Value)
在这里,我们打印元素名称和元素文本。
Catch e As XmlException
Console.WriteLine("XML parse error")
我们检查 XML 解析错误。
$ ./readxml.exe
language: Python
language: Ruby
language: Javascript
language: C#
示例输出。
文件和目录
.NET 框架提供了其他类,我们可以使用这些类来处理文件和目录。
File
类是更高级别的类,具有用于文件创建,删除,复制,移动和打开的共享方法。 这些方法使工作更加轻松。
Option Strict On
Imports System.IO
Module Example
Sub Main()
Try
Dim sw As StreamWriter
sw = File.CreateText("cars")
sw.WriteLine("Toyota")
sw.WriteLine("Skoda")
sw.WriteLine("BMW")
sw.WriteLine("Volkswagen")
sw.WriteLine("Volvo")
sw.Close()
Catch e As IOException
Console.WriteLine("IO error")
End Try
End Sub
End Module
在示例中,我们创建了一个cars
文件,并在其中写入了一些汽车名称。
sw = File.CreateText("cars")
CreateText()
方法创建或打开一个文件,用于写入 UTF-8 编码的文本。 它返回一个StreamWriter
对象。
sw.WriteLine("Toyota")
sw.WriteLine("Skoda")
...
我们向流写入两行。
Option Strict On
Imports System.IO
Module Example
Sub Main()
If File.Exists("cars")
Console.WriteLine(File.GetCreationTime("cars"))
Console.WriteLine(File.GetLastWriteTime("cars"))
Console.WriteLine(File.GetLastAccessTime("cars"))
End If
File.Copy("cars", "newcars")
End Sub
End Module
在第二个示例中,我们显示了File
类的其他五个共享方法。
If File.Exists("cars")
Exists()
方法确定指定的文件是否存在。
Console.WriteLine(File.GetCreationTime("cars"))
Console.WriteLine(File.GetLastWriteTime("cars"))
Console.WriteLine(File.GetLastAccessTime("cars"))
我们得到指定文件的创建时间,上次写入时间和上次访问时间。
File.Copy("cars", "newcars")
Copy()
方法复制文件。
My.Computer.FileSystem
是和对象,它提供用于处理驱动器,文件和目录的属性和方法。
Option Strict On
Imports System.IO
Module Example
Sub Main()
Try
My.Computer.FileSystem.CreateDirectory("temp")
My.Computer.FileSystem.CreateDirectory("newdir")
My.Computer.FileSystem.MoveDirectory("temp", "temporary")
Catch e As IOException
Console.WriteLine("Cannot create directories")
Console.WriteLine(e.Message)
End Try
End Sub
End Module
我们将使用上述对象中的两种方法。
My.Computer.FileSystem.CreateDirectory("temp")
CreateDirectory()
方法创建一个新目录。
My.Computer.FileSystem.MoveDirectory("temp", "temporary")
MoveDirectory()
方法为指定的目录提供一个新名称。
DirectoryInfo
和Directory
具有用于创建,移动和枚举目录和子目录的方法。
Option Strict On
Imports System.IO
Module Example
Dim subDir As IO.DirectoryInfo
Dim dir As New IO.DirectoryInfo("../io")
Dim fileName As String
Dim files As String() = Directory.GetFiles("../io")
Dim dirs As DirectoryInfo() = dir.GetDirectories()
Sub Main()
For Each subDir In dirs
Console.WriteLine(subDir.Name)
Next
For Each fileName In files
Console.WriteLine(fileName)
Next
End Sub
End Module
我们使用DirectoryInfo
类遍历特定目录并打印其内容。
Dim dir As New IO.DirectoryInfo("../io")
我们将显示该目录(io
)的内容。
Dim files As String() = Directory.GetFiles("../io")
我们使用共享的GetFiles()
方法获取io
目录的所有文件。
Dim dirs As DirectoryInfo() = dir.GetDirectories()
我们得到所有目录。
For Each subDir In dirs
Console.WriteLine(subDir.Name)
Next
在这里,我们遍历目录并将其名称打印到控制台。
For Each fileName In files
Console.WriteLine(fileName)
Next
在这里,我们遍历文件数组并将其名称打印到控制台。
$ ./showcontents.exe
newdir
temp
temporary
../io/append.exe
../io/append.vb
../io/append.vb~
../io/author
../io/cars
...
示例的输出。
在本章中,我们介绍了 Visual Basic 中的输入和输出操作。
C# 教程
这是 C# 教程。 在本教程中,您将学习 C# 语言的基础知识和一些高级主题。 本教程适合初学者和中级程序员。
目录
C#
C# 是一种现代的,高级的,通用的,面向对象的编程语言。 它是.NET 框架的主要语言。 该语言的设计目标是软件健壮性,耐用性和程序员生产率。 它可用于在 PC 或嵌入式系统上创建控制台应用,GUI 应用,Web 应用。
相关教程
C# 中的日期和时间文章介绍了 C# 语言中的日期和时间。 用 C# 读取网页显示了几种用 C# 读取网页的方法。
Java 与 C# 类似。 在 Java 教程中进行了介绍。 C# Winforms 教程显示了如何在 Winforms 中创建 GUI 界面。
C# 语言
在 C# 教程的这一部分中,我们介绍 C# 编程语言。
目标
本教程的目标是使您开始使用 C# 编程语言。 本教程涵盖了 C# 语言的核心,包括变量,数组,控件结构和其他核心功能。 本教程使用命令行编译器来构建应用。 它不包括图形界面开发或可视 IDE。
C#
C# 是一种现代的,高级的,通用的,面向对象的编程语言。 它是 .NET 框架的主要语言。 它支持功能,过程,通用,面向对象和面向组件的编程学科。 该语言的设计目标是软件健壮性,耐用性和程序员生产率。 它可用于在 PC 和嵌入式系统上创建控制台应用,GUI 应用,Web 应用。 C# 由 Microsoft 公司创建。 名称“C Sharp”是受音乐符号启发的,其中 Sharp 表示应将书面音调调高半音。
.NET 核心
.NET Core 提供了一个快速的模块化平台,用于创建在 Windows,Linux 和 macOS 上运行的服务器应用。 它是由 Microsoft 和 .NET 社区在 GitHub 上维护的开源通用开发平台。
$ dotnet --version
3.0.100
为了使用 .NET Core,我们需要下载并安装.NET Core SDK。 .NET Core 3 支持 C# 8.0。
编译 C# 程序
安装.NET Core SDK 之后,我们可以构建第一个 C# 程序。
$ dotnet new console -o Simple
使用dotnet new console
命令,我们创建一个新的控制台应用。
Program.cs
using System;
namespace Simple
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("This is C#");
}
}
}
这是一个简单的 C# 程序,它将消息打印到控制台。
$ dotnet run
This is C#
我们使用dotnet run
编译并运行一个简单的 C# 程序。
Visual Studio 代码
Visual Studio Code 是一种轻量级,功能强大的现代源代码编辑器,可用于 Windows,macOS 和 Linux。 它具有对 JavaScript,TypeScript 和 Node.js 的内置支持,并具有丰富的扩展生态系统,可扩展其他语言和运行时环境,包括 C# 和.NET Core。
它包括对调试,嵌入式 Git 控制,语法突出显示,智能代码完成,代码段和代码重构的支持。
数据来源
以下资源用于创建本教程:
- .NET Core 指南
- C Sharp 编程语言
- C# 语言规范
在 C# 教程的这一部分中,我们介绍了 C# 语言。
C# 语法结构
像人类语言一样,计算机语言也具有词汇结构。 C# 程序的源代码由令牌组成。 令牌是原子代码元素。 在 C# 中,我们具有注释,变量,字面值,空格,运算符,定界符和关键字。
C# 程序由 Unicode 字符集中的字符组成。
C# 注释
注释被人类用来阐明源代码。 C# 中有三种类型的注释。 单行注释,多行注释和 XML 注释。 可以将 XML 注释提取到 HTML 文件。
多行注释用/* */
字符括起来。 单行注释以两个正斜杠开头。
Program.cs
using System;
/*
This is comments.cs
Author: Jan Bodnar
ZetCode 2019
*/
namespace Comments
{
// Program starts here
class Program
{
static void Main(string[] args)
{
Console.WriteLine("This is Comments program");
}
}
}
C# 编译器将忽略注释。
/*
This is comments.cs
/* Author: Jan Bodnar */
ZetCode 2019
*/
注释不能嵌套。 上面的代码无法编译。
C# 空白字符
C# 中的空格字符用于分隔源文件中的标记。 它还用于提高源代码的可读性。
int i = 0;
在某些地方需要空格。 例如,在int
关键字和变量名之间。 在其他地方,禁止使用空格。 它们不能出现在变量标识符或语言关键字中。
int a=1;
int b = 2;
int c = 3;
标记之间放置的空间量与 C# 编译器无关。
C# 变量
变量是保存值的标识符。 在编程中,我们说我们为变量分配了一个值。 从技术上讲,变量是对存储值的计算机内存的引用。 变量名称可以包含字母数字字符和下划线。 标识符可以以字符或下划线开头。 它可能不能以数字开头。 变量名称区分大小写。 这意味着Name
,name
和NAME
引用了三个不同的变量。 变量名称也不能与语言关键字匹配。 (实际上,如果在关键字前面加上@
字符,则可以使用关键字作为标识符。但这不是一个好的编程习惯。)
string name23;
int _col;
Date birth_date;
这些是有效的 C# 标识符。
string 23name;
int %col;
Date birth date;
这些是无效的 C# 标识符。
Program.cs
using System;
namespace Identifiers
{
class Program
{
static void Main(string[] args)
{
string name = "Robert";
string Name = "Julia";
Console.WriteLine(name);
Console.WriteLine(Name);
}
}
}
标识符区分大小写。 Name
和name
是两个不同的标识符。 在 Visual Basic(C# 语言的表亲)中,这是不可能的。 用这种语言,变量名不区分大小写。
$ dotnet run
Robert
Julia
这是程序输出。
C# 字面值
字面值是类型的特定值的字面值表示。 字面值类型包括布尔值,整数,浮点数,字符串,字符和日期。 从技术上讲,字面值将在编译时分配一个值,而变量将在运行时分配。
int age = 29;
string nationality = "Hungarian";
在这里,我们为变量分配了两个字面值。 数字 29 和字符串"Hungarian"
是字面值。
Program.cs
using System;
namespace Literals
{
class Program
{
static void Main(string[] args)
{
bool sng = true;
string name = "James";
string job = null;
double weight = 68.5;
DateTime born = DateTime.Parse("November 12, 1987");
Console.WriteLine("His name is {0}", name);
if (sng)
{
Console.WriteLine("He is single");
} else
{
Console.WriteLine("He is in a relationship");
}
Console.WriteLine("His job is {0}", job);
Console.WriteLine("He weighs {0} kilograms", weight);
Console.WriteLine("He was born in {0}",
string.Format("{0:yyyy}", born));
}
}
}
在上面的示例中,我们有一些字面值。 bool
字面值可能具有值true
或false
。 "James"
是字符串字面值。 null
代表任何数据类型的默认值。 数字 23 是Integer
字面值。 数字 68.5 是浮点字面值。 最后,1987 年 11 月 12 日是日期字面值。
$ dotnet run
His name is James
He is single
His job is
He weighs 68.5 kilograms
He was born in 1987
这是程序的输出。
C# 运算符
运算符是用于对某个值执行操作的符号。 表达式中使用运算符来描述涉及一个或多个操作数的运算。
+ - * / % ^ & | ! ~
= += -= *= /= %= ^= ++ --
== != < > &= >>= <<= >= <=
|| && >> << ?:
这是 C# 运算符的部分列表。 我们将在本教程的后面部分讨论运算符。
C# 分隔符
分隔符是一个或多个字符的序列,用于指定纯文本或其他数据流中单独的独立区域之间的边界。
[ ] ( ) { } , : ;
string language = "C#";
双精度字符用于标记字符串的开头和结尾。 分号(;
)字符用于结束每个 C# 语句。
Console.WriteLine("Today is {0}", DateTime.Today.ToString("M/d"));
括号(圆括号)用于标记方法签名。 签名由方法参数组成。 圆括号用于表示求值值。
int[] array = new int[5] {1, 2, 3, 4, 5};
方括号[]
用于表示数组类型。 它们还用于访问或修改数组元素。 圆括号{}
也用于初始化数组。 花括号也用于变量插值或包围方法或类的主体。
int a, b, c;
逗号字符可用于在同一行代码上使用多个声明。
C# 关键字
关键字是 C# 语言中的保留字。 关键字用于在计算机程序中执行特定任务。 例如,定义变量,执行重复性任务或执行逻辑操作。
C# 包含丰富的关键字。 其中许多内容将在本教程中进行解释。 关键字包括if
,else
,for
,while
,base
,false
,float
,catch
,this
等。
Program.cs
using System;
namespace Keywords
{
class Program
{
static void Main(string[] args)
{
int i;
for(i = 0; i<= 5; i++)
{
Console.WriteLine(i);
}
}
}
}
在上面的示例中,我们使用了几个关键字。 using
,public
,static
,void
,int
和for
是 C# 关键字。
C# 约定
约定是程序员在编写源代码时遵循的最佳实践。 每种语言可以有自己的约定集。 约定不是严格的规则; 它们只是编写高质量代码的建议。 我们提到了许多 C# 程序员都认可的一些约定。 (并且通常也被其他程序员使用)。
- 类,接口和枚举以大写字母开头。
- 接口名称以 I 字母开头。
- 注释放在单独的行中,而不是在代码行的末尾。
- 方法名称以大写字母开头。
- 每行仅放置一个语句或声明。
- 标识符易于阅读且有意义。
- 标识符使用 Pascal 大小写编写,例如随后的每个单词都以大写字母开头。
- 同时使用
public
关键字和static
关键字之前。 Main()
方法的参数名称称为args
。- 常量以大写形式编写。
- 代码块中的大括号从新行开始。
在 C# 教程的这一部分中,我们介绍了 C# 语言的基本词汇。
C# 基础
在 C# 教程的这一部分中,我们将介绍 C# 语言的基本编程概念。 我们介绍非常基本的程序。 我们处理变量,常量和基本数据类型。 我们在控制台上读写; 我们提到了变量插值。
C# 简单示例
我们从一个非常简单的代码示例开始。
Program.cs
using System;
namespace Simple
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("This is C#");
}
}
}
这是我们的第一个 C# 程序。 它将在控制台中显示"This is C#"
消息。 我们将逐行解释。
using System;
using
关键字将特定的名称空间导入我们的程序。 创建命名空间是为了将命名实体与其他实体进行分组和/或区分。 这样可以防止名称冲突。 这行是 C# 语句。 每个语句以分号结尾。
namespace Simple
{
我们声明一个名称空间。 命名空间用于组织代码并防止名称冲突。
class Program
{
...
}
每个 C# 程序都是结构化的。 它由类及其成员组成。 类是 C# 程序的基本构建块。 上面的代码是一个类定义。 该定义具有一个主体,该主体以左花括号({
)开始,以右花括号(}
)结尾。
static void Main(string[] args)
{
...
}
Main()
是一种方法。 方法是为执行特定工作而创建的一段代码。 我们没有将所有代码放在一个地方,而是将其分为多个部分,称为方法。 这为我们的应用带来了模块化。 每个方法都有一个主体,我们在其中放置语句。 方法的主体用大括号括起来。
Main()
方法的特定工作是启动应用。 它是每个控制台 C# 程序的入口点。 该方法声明为static
。 无需创建Program
类的实例即可调用此静态方法。 首先,我们需要启动应用,然后,我们能够创建类的实例。 Main()
方法具有args
参数,该参数存储命令行参数。
Console.WriteLine("This is C#");
在此代码行中,我们将"This is C#"
字符串打印到控制台。 要将消息打印到控制台,我们使用Console
类的WriteLine()
方法。 该类表示控制台应用的标准输入,输出和错误流。 请注意,Console
类是System
命名空间的一部分。 这行是使用using System;
语句导入名称空间的原因。 如果不使用该语句,则必须使用WriteLine()
方法的完全限定名称:System.Console.WriteLine("This is C#");
。
$ dotnet run
This is C#
执行程序将得到以上输出。
C# 控制台读取值
我们也可以使用Console
类读取值。
Program.cs
using System;
namespace ReadLine
{
class Program
{
static void Main(string[] args)
{
Console.Write("Enter your name: ");
string name = Console.ReadLine();
Console.WriteLine("Hello {0}", name);
}
}
}
第二个程序从控制台读取一个值并打印出来。
string name = Console.ReadLine();
我们从终端读取一行。 当我们按下Enter
键时,输入将分配给name
变量。 输入存储在name
变量中,该变量声明为string
类型。
Console.WriteLine("Hello {0}", name);
在此代码行中,我们进行字符串格式化。 {0}
指示符替换为name
变量的值。
$ dotnet run
Enter your name: Jan
Hello Jan
这是第二个程序的输出。
C# 命令行参数
C# 程序可以接收命令行参数。 当我们运行程序时,它们会遵循程序的名称。
Program.cs
using System;
namespace CommandLineArgs
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < args.Length; i++)
{
Console.WriteLine("{0}", args[i]);
}
}
}
}
命令行参数可以传递给Main()
方法。
public static void Main(string[] args)
Main()
方法接收命令行参数的字符串数组。
for (int i=0; i<args.Length; i++)
{
Console.WriteLine("{0}", args[i]);
}
我们使用for
循环遍历这些参数的数组,并将它们打印到控制台。 Length
属性给出数组中元素的数量。 循环和数组将在后面更详细地描述。
$ dotnet run 1 2 3
1
2
3
我们提供了三个数字作为命令行参数,并将它们打印到控制台上。
C# 变量
变量是存储数据的地方。 变量具有名称和数据类型。 数据类型确定可以为变量分配哪些值,例如整数,字符串或布尔值。 随着时间的推移,程序变量可以获得相同数据类型的各种值。 在对变量进行任何引用之前,始终将变量初始化为其类型的默认值。
Program.cs
using System;
namespace Variables
{
class Program
{
static void Main(string[] args)
{
string city = "New York";
string name = "Paul"; int age = 35;
string nationality = "American";
Console.WriteLine(city);
Console.WriteLine(name);
Console.WriteLine(age);
Console.WriteLine(nationality);
city = "London";
Console.WriteLine(city);
}
}
}
在示例中,我们有四个变量。
string city = "New York";
我们声明一个字符串类型的city
变量,并将其初始化为"New York"
值。
string name = "Paul"; int age = 35;
我们声明并初始化另外两个变量。 我们可以将两个语句放在一行上。 但是出于可读性原因,每个语句应放在单独的行上。
Console.WriteLine(city);
Console.WriteLine(name);
Console.WriteLine(age);
Console.WriteLine(nationality);
我们将变量的值打印到终端。
city = "London";
我们为city
变量分配一个新值。
$ dotnet run
New York
Paul
35
American
London
这是示例的输出。
var
关键字
可以使用var
关键字隐式地键入方法范围内的变量。 变量始终是强类型,但是使用var
时,C# 编译器从赋值的右侧推断类型。
Program.cs
using System;
namespace ImplicitType
{
class Program
{
static void Main(string[] args)
{
var name = "Peter";
var age = 23;
Console.WriteLine("{0} is {1} years old", name, age);
name = "Jozef";
age = 32;
Console.WriteLine("{0} is {1} years old", name, age);
Console.WriteLine(name.GetType());
Console.WriteLine(age.GetType());
}
}
}
在程序中,我们有两个隐式类型的变量。
var name = "Peter";
var age = 23;
在分配的左侧,我们使用var
关键字。 name
变量为string
类型,age
为int
类型。 从分配的右侧推断类型。
Console.WriteLine(name.GetType());
Console.WriteLine(age.GetType());
我们用GetType()
确定变量的类型。
$ dotnet run
Peter is 23 years old
Jozef is 32 years old
System.String
System.Int32
这是输出。
C# List
集合
尽管变量保留单个值,但可以将多个值添加到集合中。 List
是元素的序列,可以通过使用[]
进行索引操作来访问。 列表中的元素可以使用例如 foreach
关键字。
Program.cs
using System;
using System.Collections.Generic;
namespace ListCollection
{
class Program
{
static void Main(string[] args)
{
var words = new List<string> { "stone", "rock", "falcon", "sky" };
Console.WriteLine(words[2]);
Console.WriteLine();
foreach (var word in words)
{
Console.WriteLine(word);
}
}
}
}
在示例中,我们处理单词列表。
using System.Collections.Generic;
为了使用List
,我们需要导入System.Collections.Generic
命名空间。
var words = new List<string> { "stone", "rock", "falcon", "sky" };
使用new
关键字创建一个列表对象。 在尖括号<>
之间,我们指定列表元素的数据类型。 该列表用大括号{}
中的元素初始化。
Console.WriteLine(words[2]);
在这里,我们将第三个元素打印到控制台。 (索引从 0 开始。)
foreach (var word in words)
{
Console.WriteLine(word);
}
使用foreach
循环,我们遍历列表的所有元素并将其打印到控制台。 在每个循环中,word
变量都被指定为列表中的元素之一。
$ dotnet run
falcon
stone
rock
falcon
sky
This is the output.
C# 丢弃
丢弃是特殊的只写变量,用于丢弃程序员不感兴趣的值。 _
(下划线字符)用于丢弃。
Program.cs
using System;
namespace SimpleDiscard
{
class Program
{
static void Main(string[] args)
{
var vals = (1, 2, 3, 4, 5, 6);
(int x, int y, int z, _, _, _) = vals;
Console.WriteLine(x);
Console.WriteLine(y);
Console.WriteLine(z);
}
}
}
该示例定义值的元组。 通过使用解构操作,我们将元组的值分配给变量。
var vals = (1, 2, 3, 4, 5, 6);
我们定义了六个值的元组。 元组是有序的异构元素的集合。
(int x, int y, int z, _, _, _) = vals;
说,我们只对元组的前三个值感兴趣。 在分配的左侧,我们定义了三个变量:x
,y
和y
。 由于vals
元组具有六个元素,因此我们也需要在左侧具有六个变量。 在这里,我们可以将_
字符用于其余变量。 通过这种方式,我们表明当前这些值对我们并不重要,我们将不使用它们。
C# Range()
方法
Range()
方法生成指定范围内的数字序列。 第一个参数是序列中第一个整数的值。 第二个参数是要生成的连续整数的数量。
Program.cs
using System;
using System.Linq;
namespace RangeEx
{
class Program
{
static void Main(string[] args)
{
foreach(var el in Enumerable.Range(1, 10))
{
Console.WriteLine(el);
}
}
}
}
该示例使用foreach
循环将值1..10
打印到终端。 整数序列是用Range()
方法生成的。
C# 常数
与变量不同,常量保留其值。 一旦初始化,便无法修改。 使用const
关键字创建常量。
Program.cs
using System;
namespace Constants
{
class Program
{
static void Main(string[] args)
{
const int WIDTH = 100;
const int HEIGHT = 150;
int var = 40;
var = 50;
// WIDTH = 110;
}
}
}
在此示例中,我们声明两个常量和一个变量。
const int WIDTH = 100;
const int HEIGHT= 150;
我们使用const
关键字通知编译器我们声明了一个常量。 按照惯例,用大写字母写常量。
int var = 40;
var = 50;
我们声明并初始化一个变量。 稍后,我们为变量分配一个新值。
// WIDTH = 110;
使用常数是不可能的。 如果取消注释此行,则会出现编译错误。
C# 字符串格式
从变量构建字符串是编程中非常常见的任务。 C# 具有string.Format()
方法来格式化字符串。
Program.cs
using System;
namespace StringFormat
{
class Program
{
static void Main(string[] args)
{
int age = 34;
string name = "William";
string msg = string.Format("{0} is {1} years old.", name, age);
Console.WriteLine(msg);
}
}
}
字符串在 C# 中是不可变的。 我们无法修改现有字符串。 我们必须从现有字符串和其他类型创建一个新字符串。 在代码示例中,我们创建一个新字符串。 我们还使用来自两个变量的值。
int age = 34;
string name = "William";
在这里,我们定义两个变量。
string msg = string.Format("{0} is {1} years old.", name, age);
我们使用内置字符串类的Format()
方法。 {0}
和{1}
是求值变量的地方。 数字代表变量的位置。 {0}
计算得出第一个提供的变量,{1}
计算得出第二个变量。
$ dotnet run
William is 34 years old.
这是程序的输出。
C# 字符串插值
一些动态语言(如 Perl,PHP 或 Ruby)支持字符串/变量插值。 变量插值是用字符串字面值中的值替换变量。 从 C# 6.0 开始,C# 支持变量插值。
Program.cs
using System;
namespace VariableInterpolation
{
class Program
{
static void Main(string[] args)
{
string name = "Peter";
int age = 34;
string msg = $"{name} is {age} years old";
Console.WriteLine(msg);
}
}
}
在代码示例中,我们使用变量插值构建字符串。
string msg = $"{name} is {age} years old";
字符串前面带有$
字符,并且变量在大括号内。
$ dotnet run
Peter is 34 years old
This is the output of the program.
本章介绍了 C# 语言的一些基础知识。
C# 数据类型
在 C# 教程的这一部分中,我们将讨论数据类型。
计算机程序(包括电子表格,文本编辑器,计算器或聊天客户端)可以处理数据。 用于各种数据类型的工具是现代计算机语言的基本组成部分。 数据类型是一组值以及对这些值的允许操作。
C# 数据类型
数据类型是一组值,以及对这些值的允许操作。
C# 中的两种基本数据类型是值类型和引用类型。 基本类型(字符串除外),枚举,元组和结构是值类型。 类,字符串,接口,数组和委托是引用类型。 每种类型都有一个默认值。 引用类型在堆上创建。 引用类型的生存期由 .NET 框架管理。 引用类型的默认值为空引用。 分配给引用类型的变量会创建引用的副本,而不是引用值的副本。 值类型在栈上创建。 生存期由变量的生存期决定。 分配给值类型的变量会创建要分配的值的副本。 值类型具有不同的默认值。 例如,布尔默认值为false
,十进制为 0,字符串为空字符串""
。
C# 布尔值
我们的世界建立了双重性。 有天地,水火,阴阳,男人和女人,爱与恨。 在 C# 中,bool
数据类型是具有以下两个值之一的原始数据类型:true
或false
。 这是一种基本数据类型,在计算机程序中非常常见。
快乐的父母正在等待孩子的出生。 他们为两种可能性都选择了名称。 如果要成为男孩,他们选择了约翰。 如果要成为女孩,他们会选择维多利亚。
Program.cs
using System;
namespace BooleanType
{
class Program
{
static void Main(string[] args)
{
Random random = new Random();
bool male = Convert.ToBoolean(random.Next(0, 2));
if (male)
{
Console.WriteLine("We will use name John");
}
else
{
Console.WriteLine("We will use name Victoria");
}
}
}
}
该程序使用随机数生成器来模拟我们的情况。
Random random = new Random();
我们创建一个Random
对象,该对象用于计算随机数。 它是系统名称空间的一部分。
bool male = Convert.ToBoolean(random.Next(0, 2));
Next()
方法返回指定范围内的随机数。 下限包括在内,上限不包括在内。 换句话说,我们收到 0 或 1。随后Convert()
方法将这些值转换为布尔值,0 转换为false
,1 转换为true
。
if (male)
{
Console.WriteLine("We will use name John");
} else
{
Console.WriteLine("We will use name Victoria");
}
如果将male
变量设置为true
,我们选择名称 John。 否则,我们选择名称 Victoria。 诸如if/else
语句之类的控制结构可使用布尔值。
$ dotnet run
We will use name John
$ dotnet run
We will use name John
$ dotnet run
We will use name Victoria
多次运行该程序将给出此示例输出。
C# 整数
整数是实数的子集。 它们写时没有小数或小数部分。 整数落在集合Z = {..., -2, -1, 0, 1, 2, ...}
中。 整数是无限的。
在计算机语言中,整数是原始数据类型。 实际上,计算机只能使用整数值的子集,因为计算机的容量有限。 整数用于计算离散实体。 我们可以有 3、4、6 个人,但不能有 3.33 个人。 我们可以有 3.33 公斤。
C# 别名 | .NET 类型 | 大小 | 范围 |
---|---|---|---|
sbyte |
System.SByte |
1 字节 | -128 至 127 |
byte |
System.Byte |
1 字节 | 0 至 255 |
short |
System.Int16 |
2 字节 | -32,768 至 32,767 |
ushort |
System.UInt16 |
2 字节 | 0 至 65,535 |
int |
System.Int32 |
4 字节 | -2,147,483,648 至 2,147,483,647 |
uint |
System.UInt32 |
4 字节 | 0 至 4,294,967,295 |
long |
System.Int64 |
8 字节 | -9,223,372,036,854,775,808 至 9,223,372,036,854,775,807 |
ulong |
System.UInt64 |
8 字节 | 0 至 18,446,744,073,709,551,615 |
可以根据我们的需要使用这些整数类型。 没有人(也许有些圣经人除外)的年龄可以超过 120、130 岁。 然后,我们可以在程序中将byte
类型用于年龄变量。 这样可以节省一些内存。
离散实体
如果我们使用整数,那么我们将处理离散实体。 我们将使用整数来计算苹果。
Program.cs
using System;
namespace Apples
{
class Program
{
static void Main(string[] args)
{
int baskets = 16;
int applesInBasket = 24;
int total = baskets * applesInBasket;
Console.WriteLine("There are total of {0} apples", total);
}
}
}
在我们的程序中,我们计算了苹果的总量。 我们使用乘法运算。
int baskets = 16;
int applesInBasket = 24;
篮子数和每个篮子中的苹果数是整数值。
int total = baskets * applesInBasket;
将这些值相乘,我们也得到一个整数。
$ dotnet run
There are total of 384 apples
这是程序的输出。
C# 整数符号
可以在 C# 中使用三种不同的表示法指定整数:十进制,十六进制和二进制。 八进制值没有符号。 据我们所知,通常使用十进制数。 十六进制数字以0x
字符开头,以0b
二进制文件开头。
Program.cs
using System;
namespace IntegerNotations
{
class Program
{
static void Main(string[] args)
{
int num1 = 31;
int num2 = 0x31;
int num3 = 0b1101;
Console.WriteLine(num1);
Console.WriteLine(num2);
Console.WriteLine(num3);
}
}
}
在程序中,我们用三个不同的符号表示三个整数。
$ dotnet run
31
49
13
默认符号是十进制。 程序以十进制显示这两个数字。 换句话说,十六进制的0x31
是十进制的 49。
使用下划线
C# 允许对数字字面值使用下划线字符,以提高值的可读性。
Program.cs
using System;
namespace UnderscoreLiterals
{
class Program
{
static void Main(string[] args)
{
var num1 = 234_321_000;
Console.WriteLine(num1);
var num2 = 0b_0110_000_100;
Console.WriteLine(num2);
}
}
}
该程序使用带下划线字符的整数字面值来提高值的可读性。
算术溢出
算术溢出是在计算产生的结果的大小大于给定寄存器或存储位置可以存储或表示的结果时发生的条件。
Program.cs
using System;
namespace OverFlow
{
class Program
{
static void Main(string[] args)
{
byte a = 254;
Console.WriteLine(a);
a++;
Console.WriteLine(a);
a++;
Console.WriteLine(a);
a++;
Console.WriteLine(a);
}
}
}
在此示例中,我们尝试分配一个超出数据类型范围的值。 这导致算术溢出。
$ dotnet run
254
255
0
1
发生溢出时,变量将重置为数据类型的下限。 (如果是字节类型,则为零。)
使用checked
关键字,可以在发生溢出时强制执行异常。
Program.cs
using System;
namespace OverflowChecked
{
class Program
{
static void Main(string[] args)
{
checked {
byte a = 254;
Console.WriteLine(a);
a++;
Console.WriteLine(a);
a++;
Console.WriteLine(a);
a++;
Console.WriteLine(a);
}
}
}
}
在该示例中,语句放置在checked
块的主体中。
$ dotnet run
254
255
Unhandled Exception: System.OverflowException: Arithmetic operation resulted in an overflow.
...
这次抛出了System.OverflowException
。
C# 浮点数
浮点数表示计算中的实数。 实数测量连续的数量,例如重量,高度或速度。 在 C# 中,我们有三种浮点类型:float
,double
和decimal
。
C# 别名 | .NET 类型 | 大小 | 精度 | 范围 |
---|---|---|---|---|
float |
System.Single |
4 字节 | 7 位数 | +-1.5 x 10^-45 至+-3.4 x 10^38 |
double |
System.Double |
8 字节 | 15-16 位数 | +-5.0 x 10^-324 至+-1.7 x 10^308 |
decimal |
System.Decimal |
16 字节 | 28-29 位小数 | +-1.0 x 10^-28 至+-7.9 x 10^28 |
上表给出了浮点类型的特征。
默认情况下,C# 程序中的实数是双精度的。 要使用其他类型,必须使用后缀。 float
编号为F/f
,decimal
编号为M/m
。
Program.cs
using System;
namespace Floats
{
class Program
{
static void Main(string[] args)
{
float n1 = 1.234f;
double n2 = 1.234;
decimal n3 = 1.234m;
Console.WriteLine(n1);
Console.WriteLine(n2);
Console.WriteLine(n3);
Console.WriteLine(n1.GetType());
Console.WriteLine(n2.GetType());
Console.WriteLine(n3.GetType());
}
}
}
在上面的程序中,我们对浮点数使用三种不同的字面值符号。
float n1 = 1.234f;
f
后缀用于float
数字。
double n2 = 1.234;
如果我们不使用后缀,则为double
数字。 我们可以选择使用d
后缀。
Console.WriteLine(n1.GetType());
GetType()
方法返回数字的类型。
$ dotnet run
1.234
1.234
1.234
System.Single
System.Double
System.Decimal
这是输出。
我们可以使用各种语法来创建浮点值。
Program.cs
using System;
namespace Notations
{
class Program
{
static void Main(string[] args)
{
float n1 = 1.234f;
float n2 = 1.2e-3f;
float n3 = (float)1 / 3;
Console.WriteLine(n1);
Console.WriteLine(n2);
Console.WriteLine(n3);
}
}
}
我们有三种创建浮点值的方法。 第一种是使用小数点的“正常”方式。 第二种使用科学计数法。 最后一个是数字运算的结果。
float n2 = 1.2e-3f;
这是浮点数的科学表示法。 也称为指数表示法,它是一种写数字太大或太小而不能方便地用标准十进制表示法写的方式。
float n3 = (float) 1 / 3;
(float)
构造称为转换。 默认情况下,除法运算将返回整数。 通过强制转换,我们得到一个浮点数。
$ dotnet run
1.234
0.0012
0.3333333
这是上面程序的输出。
float
和double
类型不精确。
Program.cs
using System;
namespace InExact
{
class Program
{
static void Main(string[] args)
{
double n1 = 0.1 + 0.1 + 0.1;
double n2 = 1 / 3.0;
if (n1 == n2)
{
Console.WriteLine("Numbers are equal");
}
else
{
Console.WriteLine("Numbers are not equal");
}
}
}
}
比较浮点值时应格外小心。
$ dotnet run
Numbers are not equal
而且数字不相等。
假设一个短跑运动员跑了 1 个小时,跑了 9.87 秒。 他的公里/小时速度是多少?
Program.cs
using System;
namespace Sprinter
{
class Program
{
static void Main(string[] args)
{
float distance = 0.1f;
float time = 9.87f / 3600;
float speed = distance / time;
Console.WriteLine("The average speed of a sprinter is {0} km/h", speed);
}
}
}
在此示例中,必须使用浮点值。
float distance = 0.1f;
100m 是 0.1 km。
float time = 9.87f / 3600;
9.87s 是9.87 / (60 * 60)
h。
float speed = distance / time;
为了获得速度,我们将距离除以时间。
$ dotnet run
The average speed of a sprinter is 36.47416 km/h
This is the output of the program.
C# 枚举
枚举类型(也称为枚举或枚举)是由一组命名值组成的数据类型。 可以将任何枚举器分配为已声明为具有枚举类型的变量作为值。 枚举使代码更具可读性。
Program.cs
using System;
namespace Enumerations
{
enum Days
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
class Program
{
static void Main(string[] args)
{
Days day = Days.Monday;
if (day == Days.Monday)
{
Console.WriteLine("It is Monday");
}
Console.WriteLine(day);
foreach (int i in Enum.GetValues(typeof(Days)))
{
Console.WriteLine(i);
}
}
}
}
在我们的代码示例中,我们为工作日创建一个枚举。
enum Days
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
使用enum
关键字创建枚举。 星期一,星期二,...星期日实际上存储着数字0..6
。
Days day = Days.Monday;
我们有一个名为day
的变量,其类型为Days
。 它被初始化为星期一。
if (day == Days.Monday)
{
Console.WriteLine("It is Monday");
}
与将日变量与某个数字进行比较相比,此代码更具可读性。
Console.WriteLine(day);
该行将在星期一打印到控制台。
foreach (int i in Enum.GetValues(typeof(Days)))
{
Console.WriteLine(i);
}
此循环将0..6
打印到控制台。 我们得到enum
值的基础类型。 对于计算机,enum
只是一个数字。 typeof
是用于获取类型的System.Type
对象的运算符。 GetValues()
方法需要它。 此方法返回指定枚举值的数组。 foreach
关键字逐个元素地遍历数组并将其打印到终端。
我们会进一步进行枚举。
Program.cs
using System;
using System;
namespace Seasons
{
public enum Seasons : byte
{
Spring = 1,
Summer = 2,
Autumn = 3,
Winter = 4
}
class Program
{
static void Main(string[] args)
{
Seasons s1 = Seasons.Spring;
Seasons s2 = Seasons.Autumn;
Console.WriteLine(s1);
Console.WriteLine(s2);
}
}
}
季节可以很容易地用作枚举。 我们可以为enum
指定基础类型,并且可以为其提供确切的值。
public enum Seasons : byte
{
Spring = 1,
Summer = 2,
Autumn = 3,
Winter = 4
}
使用冒号和数据类型,我们指定enum
的基础类型。 我们还给每个成员一个特定的号码。
Console.WriteLine(s1);
Console.WriteLine(s2);
这两行将enum
值打印到控制台。
$ dotnet run
Spring
Autumn
This is the output of the program.
C# 元组
元组是异类数据值的有序的,不变的列表。 元组是值类型。 元组必须至少包含两个元素。 元组用圆括号()
定义。
Program.cs
using System;
namespace Tuples
{
class Program
{
static void Main(string[] args)
{
var words = ("sky", "blue", "rock", "fountain");
Console.WriteLine(words);
Console.WriteLine(words.Item1);
Console.WriteLine(words.Item2);
var words2 = (w1: "forest", w2: "deep", w3: "sea");
Console.WriteLine(words2.w1);
Console.WriteLine(words2.w2);
Console.WriteLine(words2.w3);
}
}
}
在示例中,我们定义了两个元组。
var words = ("sky", "blue", "rock", "fountain");
这是一个未命名的元组定义。
Console.WriteLine(words);
我们将元组的所有元素打印到控制台。
Console.WriteLine(words.Item1);
Console.WriteLine(words.Item2);
我们打印前两个元素。 我们使用特殊的Item1
,Item2
,...属性访问未命名元组的元素。
var words2 = (w1: "forest", w2: "deep", w3: "sea");
这是一个命名元组的定义。
Console.WriteLine(words2.w1);
Console.WriteLine(words2.w2);
Console.WriteLine(words2.w3);
我们通过元素名称访问元素。
$ dotnet run
(sky, blue, rock, fountain)
sky
blue
forest
deep
sea
This is the output.
C# 字符串和字符
string
是代表计算机程序中文本数据的数据类型。 C# 中的字符串是 Unicode 字符序列。 char
是单个 Unicode 字符。 字符串用双引号引起来。
由于字符串在每种编程语言中都非常重要,因此我们将为它们专门整整一章。 这里我们仅举一个小例子。
Program.cs
using System;
namespace Strings
{
class Program
{
static void Main(string[] args)
{
string word = "ZetCode";
char c = word[0];
Console.WriteLine(c);
}
}
}
程序将'Z'
字符打印到终端。
string word = "ZetCode";
在这里,我们创建一个字符串变量,并为其分配"ZetCode"
值。
char c = word[0];
string
是 Unicode 字符数组。 我们可以使用数组访问符号从字符串中获取特定字符。 方括号内的数字是字符数组的索引。 索引从零开始计数。 这意味着第一个字符的索引为 0。
$ dotnet run
Z
该程序将"ZetCode"
字符串的第一个字符打印到控制台。
C# 数组
数组是处理元素集合的复杂数据类型。 每个元素都可以通过索引访问。 数组的所有元素必须具有相同的数据类型。
我们将整章专门介绍数组。 这里我们仅显示一个小例子。
Program.cs
using System;
namespace ArrayEx
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[5];
numbers[0] = 3;
numbers[1] = 2;
numbers[2] = 1;
numbers[3] = 5;
numbers[4] = 6;
int len = numbers.Length;
for (int i = 0; i < len; i++)
{
Console.WriteLine(numbers[i]);
}
}
}
}
在此示例中,我们声明一个数组,用数据填充它,然后将数组的内容打印到控制台。
int[] numbers = new int[5];
我们声明一个整数数组,该数组最多可以存储五个整数。 因此,我们有五个元素组成的数组,索引为0..4
。
numbers[0] = 3;
numbers[1] = 2;
numbers[2] = 1;
numbers[3] = 5;
numbers[4] = 6;
在这里,我们为创建的数组分配值。 我们可以通过数组访问符号访问数组的元素。 它由数组名称和方括号组成。 在方括号内,我们指定所需元素的索引。
int len = numbers.Length;
每个数组都有一个Length
属性,该属性返回数组中的元素数。
for (int i=0; i<len; i++)
{
Console.WriteLine(numbers[i]);
}
我们遍历数组并将数据打印到控制台。
C# DateTime
DateTime
是一种值类型。 它代表时间的瞬间,通常表示为日期和时间。
Program.cs
using System;
namespace DateTimeEx
{
class Program
{
static void Main(string[] args)
{
DateTime now = DateTime.Now;
System.Console.WriteLine(now);
System.Console.WriteLine(now.ToShortDateString());
System.Console.WriteLine(now.ToShortTimeString());
}
}
}
我们以三种不同的格式显示今天的日期:日期&时间,日期和时间。
DateTime now = DateTime.Now;
获取一个DateTime
对象,该对象设置为此计算机上的当前日期和时间,表示为本地时间。
System.Console.WriteLine(now);
此行以完整格式打印日期。
System.Console.WriteLine(now.ToShortDateString());
System.Console.WriteLine(now.ToShortTimeString());
ToShortDateString()
返回短日期字符串格式,ToShortTimeString()
返回短时间字符串格式。
$ dotnet run
12/5/2018 8:09:56 PM
12/5/2018
8:09 PM
我们看到示例的输出。
C# 类型转换
我们经常一次处理多种数据类型。 将一种数据类型转换为另一种数据类型是编程中的常见工作。 类型转换或类型转换是指将一种数据类型的实体更改为另一种。 有两种转换类型:隐式转换和显式转换。 隐式类型转换,也称为强制转换,是编译器自动进行的类型转换。
Program.cs
using System;
namespace ImplicitTypeConversion
{
class Program
{
static void Main(string[] args)
{
int val1 = 0;
byte val2 = 15;
val1 = val2;
Console.WriteLine(val1.GetType());
Console.WriteLine(val2.GetType());
Console.WriteLine(12 + 12.5);
Console.WriteLine("12" + 12);
}
}
}
在此示例中,我们有几个隐式转换。
val1 = val2;
在这里,我们使用两种不同的类型:int
和byte
。 我们将byte
值分配给int
值。 这是一个扩大的操作。 int
值有四个字节。 字节值只有一个字节。 允许扩展转换。 如果我们想将int
分配给byte
,这将是缩短转换。 C# 编译器不允许隐式缩短转换。 这是因为在隐式缩短转换中,我们可能会无意间降低精度。 我们可以缩短转换时间,但是我们必须将其告知编译器。 我们知道自己在做什么。 可以通过显式转换来完成。
Console.WriteLine(12 + 12.5);
我们添加两个值:一个整数和一个浮点值。 结果是浮点值。 这是一个不断扩大的隐式转换。
Console.WriteLine("12" + 12);
结果为 1212。将整数转换为字符串,然后将两个字符串连接在一起。
接下来,我们将展示一些在 C# 中的显式转换。
Program.cs
using System;
namespace ExplicitTypeConversion
{
class Program
{
static void Main(string[] args)
{
double b = 13.5;
float a = (float) b;
float c = (int) a;
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
}
}
我们有三个值。 我们使用这些值进行一些显式转换。
float a = (float) b;
我们将double
值转换为float
值。 通过在两个圆括号之间指定所需的类型来进行显式转换。 在这种情况下,不会损失任何精度。 数字 13.5 可以安全地分配给这两种类型。
float c = (int) a;
我们将float
值转换为int
值。 在此语句中,我们失去了一些精度:13.5 变为 13。
$ dotnet run
13.5
13.5
13
我们看到了程序的输出。
C# 可空类型
不能为值类型分配null
字面值,可以给引用类型分配值。 使用数据库的应用处理空值。 因此,C# 语言中引入了特殊的可空类型。 可空类型是System.Nullable<T>
结构的实例。
Program.cs
using System;
class NullableType
{
static void Main()
{
Nullable<bool> male = null;
int? age = null;
Console.WriteLine(male.HasValue);
Console.WriteLine(age.HasValue);
}
}
一个简单的示例,演示可空类型。
Nullable<bool> male = null;
int? age = null;
有两种方法可以声明可为空的类型。 在Nullable<T>
通用结构中,在尖括号之间指定了类型,或者我们可以在类型后使用问号。 后者实际上是第一种表示法的简写。
$ dotnet run
False
False
这是示例的输出。
C# 转换&解析方法
有两种用于转换值的方法。
Program.cs
using System;
namespace ConvertEx
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Convert.ToBoolean(0.3));
Console.WriteLine(Convert.ToBoolean(3));
Console.WriteLine(Convert.ToBoolean(0));
Console.WriteLine(Convert.ToBoolean(-1));
Console.WriteLine(Convert.ToInt32("452"));
Console.WriteLine(Convert.ToInt32(34.5));
}
}
}
Convert
类具有许多用于转换值的方法。 我们使用其中两个。
Console.WriteLine(Convert.ToBoolean(0.3));
我们将double
值转换为bool
值。
Console.WriteLine(Convert.ToInt32("452"));
在这里,我们将string
转换为int
。
$ dotnet run
True
True
False
True
452
34
This is the output.
Program.cs
using System;
namespace ParseEx
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Int32.Parse("34"));
Console.WriteLine(Int32.Parse("-34"));
Console.WriteLine(Int32.Parse("+34"));
}
}
}
将字符串转换为整数是非常常见的任务。 当我们从数据库或 GUI 组件中获取值时,我们通常会进行此类转换。
Console.WriteLine(Int32.Parse("34"));
我们使用Int32
类的Parse()
方法将string
转换为int
值。
$ dotnet run
34
-34
34
This is the output.
在 C# 教程的这一部分中,我们介绍了数据类型及其转换。
C# 中的字符串
在 C# 教程的这一部分中,我们将更详细地处理字符串数据。 字符串在计算机语言中非常重要。 这就是为什么我们将整章专门用于 C# 中的字符串的原因。
C# 字符串定义
字符串是字符序列。 在 C# 中,字符串是 Unicode 字符序列。 它是一种数据类型,用于存储一系列数据值(通常为字节),其中元素通常根据字符编码代表字符。 当字符串从字面上出现在源代码中时,称为字符串字面值。
字符串是对象。 有两个用于处理字符串的基本类:
System.String
System.Text.StringBuilder
String
是不可变的字符序列。 StringBuilder
是可变的字符序列。
在 C# 中,string
是System.String
的别名。 string
是语言关键字,System.String
是.NET 类型。
C# 初始化字符串
有多种创建字符串的方法,它们都是不可变的和可变的。 我们将展示其中的一些。
Program.cs
using System;
using System.Text;
namespace Initialization
{
class Program
{
static void Main(string[] args)
{
char[] cdb = { 'M', 'y', 'S', 'q', 'l' };
string lang = "C#";
String ide = "NetBeans";
string db = new string(cdb);
Console.WriteLine(lang);
Console.WriteLine(ide);
Console.WriteLine(db);
StringBuilder sb1 = new StringBuilder(lang);
StringBuilder sb2 = new StringBuilder();
sb2.Append("Fields");
sb2.Append(" of ");
sb2.Append("glory");
Console.WriteLine(sb1);
Console.WriteLine(sb2);
}
}
}
该示例显示了创建System.String
和System.Text.StringBuilder
对象的几种方法。
using System.Text;
该语句可以不加限制地使用System.Text.StringBuilder
类型。
string lang = "C#";
String ide = "NetBeans";
最常见的方法是根据字符串字面值创建字符串对象。
string db = new string(cdb);
在这里,我们从字符数组创建一个字符串对象。 string
是System.String
的别名。
StringBuilder sb1 = new StringBuilder(lang);
从String
创建一个StringBuilder
对象。
StringBuilder sb2 = new StringBuilder();
sb2.Append("Fields");
sb2.Append(" of ");
sb2.Append("glory");
我们创建一个空的StringBuilder
对象。 我们将三个字符串附加到对象中。
$ dotnet run
C#
NetBeans
MySql
C#
Fields of glory
运行示例可得出此结果。
C# 字符串插值
$特殊字符前缀将字符串字面值标识为插值字符串。 插值字符串是可能包含插值表达式的字符串字面值。
字符串格式化与字符串插值相似。 本章后面将介绍它。
Program.cs
using System;
namespace Interpolation
{
class Program
{
static void Main(string[] args)
{
int age = 23;
string name = "Peter";
DateTime now = DateTime.Now;
Console.WriteLine($"{name} is {age} years old");
Console.WriteLine($"Hello, {name}! Today is {now.DayOfWeek},
it's {now:HH:mm} now");
}
}
}
该示例介绍了 C# 字符串插值。
Console.WriteLine($"{name} is {age} years old");
内插变量位于{}括号之间。
Console.WriteLine($"Hello, {name}! Today is {now.DayOfWeek},
it's {now:HH:mm} now");
插值语法可以接收表达式或格式说明符。
$ dotnet run
Peter is 23 years old
Hello, Peter! Today is Friday, it's 14:58 now
这是输出。
C# 常规字符串
常规字符串可以包含解释的转义序列,例如换行符或制表符。 常规字符串放在一对双引号之间。
Program.cs
using System;
using System.Text;
namespace RegularLiterals
{
class Program
{
static void Main(string[] args)
{
string s1 = "deep \t forest";
string s2 = "deep \n forest";
Console.WriteLine(s1);
Console.WriteLine(s2);
Console.WriteLine("C:\\Users\\Admin\\Documents");
}
}
}
该示例打印两个包含\t
和\n
转义序列的字符串。
Console.WriteLine("C:\\Users\\Admin\\Documents");
使用路径时,必须避开阴影。
$ dotnet run
deep forest
deep
forest
C:\Users\Admin\Documents
This is the output.
C# 逐字字符串
逐字字符串不解释转义序列。 逐字字符串以@
字符开头。 逐字字符串可用于多行字符串。
Program.cs
using System;
namespace VerbatimLiterals
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(@"deep \t forest");
Console.WriteLine(@"C:\Users\Admin\Documents");
var text = @"
Not marble, nor the gilded monuments
Of princes, shall outlive this powerful rhyme;
But you shall shine more bright in these contents
Than unswept stone, besmeared with sluttish time.";
Console.WriteLine(text);
}
}
}
在此代码示例中,我们使用逐字字符串。
Console.WriteLine(@"deep \t forest");
\t
特殊字符不被解释; 它仅打印到控制台。
Console.WriteLine(@"C:\Users\Admin\Documents");
使用路径时,逐字字符串很方便; 不必逃避阴影。
var text = @"
Not marble, nor the gilded monuments
Of princes, shall outlive this powerful rhyme;
But you shall shine more bright in these contents
Than unswept stone, besmeared with sluttish time.";
逐字字符串允许我们创建多行字符串。
$ dotnet run
deep \t forest
C:\Users\Admin\Documents
Not marble, nor the gilded monuments
Of princes, shall outlive this powerful rhyme;
But you shall shine more bright in these contents
Than unswept stone, besmeared with sluttish time.
This is the output.
C# 字符串是对象
字符串是对象。 它们是引用类型。 字符串是System.String
或System.Text.StringBuilder
类的实例。 由于它们是对象,因此有多种方法可用于完成各种工作。
Program.cs
using System;
namespace Objects
{
class Program
{
static void Main(string[] args)
{
string lang = "Java";
string bclass = lang.GetType().Name;
Console.WriteLine(bclass);
string parclass = lang.GetType().BaseType.Name;
Console.WriteLine(parclass);
if (lang.Equals(String.Empty))
{
Console.WriteLine("The string is empty");
}
else
{
Console.WriteLine("The string is not empty");
}
int len = lang.Length;
Console.WriteLine("The string has {0} characters", len);
}
}
}
在此程序中,我们演示了字符串是对象。 对象必须具有一个类名,一个父类,并且还必须具有一些我们可以调用的方法或要访问的属性。
string lang = "Java";
创建System.String
类型的对象。
string bclass = lang.GetType().Name;
Console.WriteLine(bclass);
我们确定lang
变量所引用的对象的类名称。
string parclass = lang.GetType().BaseType.Name;
Console.WriteLine(parclass);
接收到我们对象的父类。 所有对象都有至少一个父对象-Object
。
if (lang.Equals(String.Empty))
{
Console.WriteLine("The string is empty");
} else
{
Console.WriteLine("The string is not empty");
}
对象具有各种方法。 使用Equals()
方法,我们检查字符串是否为空。
int len = lang.Length;
Console.WriteLine("The string has {0} characters", len);
Length()
方法返回字符串的大小。
$ dotnet run
String
Object
The string is not empty
The string has 4 characters
这是stringobjects.exe
程序的输出。
C# 可变&不可变字符串
String
是不可变字符序列,而StringBuilder
是可变字符序列。 下一个示例将显示差异。
Program.cs
using System;
using System.Text;
namespace MutableImmutable
{
class Program
{
static void Main(string[] args)
{
string name = "Jane";
string name2 = name.Replace('J', 'K');
string name3 = name2.Replace('n', 't');
Console.WriteLine(name);
Console.WriteLine(name3);
StringBuilder sb = new StringBuilder("Jane");
Console.WriteLine(sb);
sb.Replace('J', 'K', 0, 1);
sb.Replace('n', 't', 2, 1);
Console.WriteLine(sb);
}
}
}
这两个对象都有替换字符串中字符的方法。
string name = "Jane";
string name2 = name.Replace('J', 'K');
string name3 = name2.Replace('n', 't');
在String
上调用Replace()
方法将导致返回新的修改后的字符串。 原始字符串不变。
sb.Replace('J', 'K', 0, 1);
sb.Replace('n', 't', 2, 1);
StringBuilder
的Replace()
方法将用新字符替换给定索引处的字符。 原始字符串被修改。
$ dotnet run
Jane
Kate
Jane
Kate
这是程序的输出。
C# 连接字符串
可以使用+
运算符或Concat()
方法添加不可变的字符串。 它们将形成一个新字符串,该字符串是所有连接字符串的链。 可变字符串具有Append()
方法,该方法可以从任意数量的其他字符串中构建一个字符串。
也可以使用字符串格式设置和插值来连接字符串。
Program.cs
using System;
using System.Text;
namespace Concatenate
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Return" + " of " + "the king.");
Console.WriteLine(string.Concat(string.Concat("Return", " of "),
"the king."));
StringBuilder sb = new StringBuilder();
sb.Append("Return");
sb.Append(" of ");
sb.Append("the king.");
Console.WriteLine(sb);
string s1 = "Return";
string s2 = "of";
string s3 = "the king.";
Console.WriteLine("{0} {1} {2}", s1, s2, s3);
Console.WriteLine($"{s1} {s2} {s3}");
}
}
}
该示例通过连接字符串创建五个句子。
Console.WriteLine("Return" + " of " + "the king.");
通过使用+运算符形成一个新的字符串。
Console.WriteLine(string.Concat(string.Concat("Return", " of "),
"the king."));
Concat()
方法连接两个字符串。 该方法是System.String
类的静态方法。
StringBuilder sb = new StringBuilder();
sb.Append("Return");
sb.Append(" of ");
sb.Append("the king.");
通过三次调用Append()
方法来创建StringBuilder
类型的可变对象。
Console.WriteLine("{0} {1} {2}", s1, s2, s3);
字符串以字符串格式形成。
Console.WriteLine($"{s1} {s2} {s3}");
最后,使用插值语法添加字符串。
$ dotnet run
Return of the king.
Return of the king.
Return of the king.
Return of the king.
Return of the king.
这是示例输出。
C# 使用引号
当我们要显示引号时,例如在直接语音中,必须对内引号进行转义。
Program.cs
using System;
namespace Quotes
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("There are many stars.");
Console.WriteLine("He said, \"Which one is your favourite?\"");
Console.WriteLine(@"
Lao Tzu has said:
""If you do not change direction, you may end up
where you are heading.""
");
}
}
}
本示例打印直接语音。
Console.WriteLine("He said, \"Which one is your favourite?\"");
在常规字符串中,该字符使用\
进行转义。
Console.WriteLine(@"
Lao Tzu has said:
""If you do not change direction, you may end up
where you are heading.""
");
在逐字字符串中,引号前面带有另一个引号。
$ dotnet run
There are many stars.
He said, "Which one is your favourite?"
Lao Tzu has said: "If you do not change direction, you may end up
where you are heading."
This is the output of the program.
C# 比较字符串
我们可以使用==
运算符比较两个字符串。
Program.cs
using System;
namespace CompareString
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("12" == "12");
Console.WriteLine("17" == "9");
Console.WriteLine("aa" == "ab");
}
}
}
在示例程序中,我们比较字符串。
$ dotnet run
True
False
False
这是程序的输出。
string.Compare()
方法比较两个指定的字符串,并返回一个整数,该整数指示排序顺序中它们的相对位置。 如果返回的值小于零,则第一个字符串小于第二个字符串。 如果返回零,则两个字符串相等。 最后,如果返回的值大于零,则第一个字符串大于第二个字符串。
Program.cs
using System;
namespace CompareString2
{
class Program
{
static void Main(string[] args)
{
string str1 = "ZetCode";
string str2 = "zetcode";
Console.WriteLine(string.Compare(str1, str2, true));
Console.WriteLine(string.Compare(str1, str2, false));
}
}
}
有一个可选的第三个ignoreCase
参数。 它确定是否应履行此案。
Console.WriteLine(string.Compare(str1, str2, true));
比较两个字符串并忽略大小写。 此行将 0 打印到控制台。
C# 字符串元素
字符串是字符序列。 字符是字符串的基本元素。
Program.cs
using System;
namespace StringElements
{
class Program
{
static void Main(string[] args)
{
char[] crs = { 'Z', 'e', 't', 'C', 'o', 'd', 'e' };
String s = new String(crs);
char c1 = s[0];
char c2 = s[(s.Length - 1)];
Console.WriteLine(c1);
Console.WriteLine(c2);
int i1 = s.IndexOf('e');
int i2 = s.LastIndexOf('e');
Console.WriteLine("The first index of character e is " + i1);
Console.WriteLine("The last index of character e is " + i2);
Console.WriteLine(s.Contains("t"));
Console.WriteLine(s.Contains("f"));
char[] elements = s.ToCharArray();
foreach (char el in elements)
{
Console.WriteLine(el);
}
}
}
}
在第一个示例中,我们将使用不可变的字符串。
char[] crs = {'Z', 'e', 't', 'C', 'o', 'd', 'e' };
String s = new String(crs);
由字符数组构成一个新的不可变字符串。
char c1 = s[0];
char c2 = s[(s.Length-1)];
使用数组访问符号,我们获得字符串的第一个和最后一个char
值。
int i1 = s.IndexOf('e');
int i2 = s.LastIndexOf('e');
通过上述方法,我们得到了字符"e"
的第一个和最后一个出现。
Console.WriteLine(s.Contains("t"));
Console.WriteLine(s.Contains("f"));
使用Contains()
方法,我们检查字符串是否包含't'字符。 该方法返回一个布尔值。
char[] elements = s.ToCharArray();
foreach (char el in elements)
{
Console.WriteLine(el);
}
ToCharArray()
方法从字符串创建一个字符数组。 我们遍历数组并打印每个字符。
$ dotnet run
Z
e
The first index of character e is 1
The last index of character e is 6
True
False
Z
e
t
C
o
d
e
This is the example output.
在第二个示例中,我们将使用可变字符串的元素。
Program.cs
using System;
using System.Text;
public class StringBuilderElements
{
static void Main()
{
StringBuilder sb = new StringBuilder("Misty mountains");
Console.WriteLine(sb);
sb.Remove(sb.Length-1, 1);
Console.WriteLine(sb);
sb.Append('s');
Console.WriteLine(sb);
sb.Insert(0, 'T');
sb.Insert(1, 'h');
sb.Insert(2, 'e');
sb.Insert(3, ' ');
Console.WriteLine(sb);
sb.Replace('M', 'm', 4, 1);
Console.WriteLine(sb);
}
}
形成可变的字符串。 我们通过删除,附加,插入和替换字符来修改字符串的内容。
sb.Remove(sb.Length-1, 1);
这行删除最后一个字符。
sb.Append('s');
删除的字符将附加回字符串。
sb.Insert(0, 'T');
sb.Insert(1, 'h');
sb.Insert(2, 'e');
sb.Insert(3, ' ');
我们在字符串的开头插入四个字符。
sb.Replace('M', 'm', 4, 1);
最后,我们在索引 4 处替换一个字符。
$ dotnet run
Misty mountains
Misty mountain
Misty mountains
The Misty mountains
The misty mountains
从输出中,我们可以看到可变字符串是如何变化的。
C# 字符串连接和拆分
Join()
连接字符串,Split()
拆分字符串。
Program.cs
using System;
namespace JoinSplit
{
class Program
{
static void Main(string[] args)
{
var items = new string[] { "C#", "Visual Basic", "Java", "Perl" };
var langs = string.Join(",", items);
Console.WriteLine(langs);
string[] langs2 = langs.Split(',');
foreach (string lang in langs2)
{
Console.WriteLine(lang);
}
}
}
}
在我们的程序中,我们将连接和分割字符串。
var items = new string[] { "C#", "Visual Basic", "Java", "Perl" };
这是一个字符串数组。 这些字符串将被连接。
string langs = string.Join(",", items);
数组中的所有单词都被加入。 我们从中构建一个字符串。 每两个字之间会有一个逗号。
string[] langs2 = langs.Split(',');
作为反向操作,我们分割了langs
字符串。 Split()
方法返回由字符分隔的单词数组。 在我们的情况下,它是一个逗号字符。
foreach (string lang in langs2)
{
Console.WriteLine(lang);
}
我们遍历数组并打印其元素。
$ dotnet run
C#,Visual Basic,Java,Perl
C#
Visual Basic
Java
Perl
这是示例的输出。
C# 常用方法
接下来,我们介绍了几个其他的常见字符串方法。
Program.cs
using System;
namespace CommonMethods
{
class Program
{
static void Main(string[] args)
{
string word = "Determination";
Console.WriteLine(word.Contains("e"));
Console.WriteLine(word.IndexOf("e"));
Console.WriteLine(word.LastIndexOf("i"));
Console.WriteLine(word.ToUpper());
Console.WriteLine(word.ToLower());
}
}
}
在上面的示例中,我们介绍了五个字符串方法。
Console.WriteLine(str.Contains("e"));
如果字符串包含特定字符,则Contains()
方法返回True
。
Console.WriteLine(str.IndexOf("e"));
IndexOf()
返回字符串中字母的第一个索引。
Console.WriteLine(str.LastIndexOf("i"));
LastIndexOf()
方法返回字符串中字母的最后一个索引。
Console.WriteLine(str.ToUpper());
Console.WriteLine(str.ToLower());
字符串的字母通过ToUpper()
方法转换为大写,并通过ToLower()
方法转换为小写。
$ dotnet run
True
1
10
DETERMINATION
determination
运行程序。
C# 字符串复制与克隆
我们将描述两种方法之间的区别:Copy()
和Clone()
。 Copy()
方法创建一个新字符串实例,该实例的值与指定的字符串相同。 Clone()
方法返回对正在克隆的字符串的引用。 它不是堆上字符串的独立副本。 它是同一字符串上的另一个引用。
Program.cs
using System;
namespace CopyClone
{
class Program
{
static void Main(string[] args)
{
string str = "ZetCode";
string cloned = (string) str.Clone();
string copied = string.Copy(str);
Console.WriteLine(str.Equals(cloned)); // prints True
Console.WriteLine(str.Equals(copied)); // prints True
Console.WriteLine(ReferenceEquals(str, cloned)); // prints True
Console.WriteLine(ReferenceEquals(str, copied)); // prints False
}
}
}
我们的示例演示了两种方法之间的区别。
string cloned = (string) str.Clone();
string copied = string.Copy(str);
字符串值被克隆并复制。
Console.WriteLine(str.Equals(cloned)); // prints True
Console.WriteLine(str.Equals(copied)); // prints True
Equals()
方法确定两个字符串对象是否具有相同的值。 所有三个字符串的内容都是相同的。
Console.WriteLine(ReferenceEquals(str, cloned)); // prints True
Console.WriteLine(ReferenceEquals(str, copied)); // prints False
ReferenceEquals()
方法比较两个引用对象。 因此,将复制的字符串与原始字符串进行比较将返回false
。 因为它们是两个不同的对象。
C# 格式化字符串
在下面的示例中,我们将格式化字符串。 .NET Framework 具有称为复合格式的功能。 Format()
和WriteLine()
方法支持它。 方法采用对象列表和复合格式字符串作为输入。 格式字符串由固定字符串和一些格式项组成。 这些格式项是与列表中的对象相对应的索引占位符。
格式项具有以下语法:
{index[,length][:formatString]}
索引组件是必需的。 它是一个从 0 开始的数字,表示对象列表中的一项。 多个项目可以引用对象列表的同一元素。 如果格式项未引用该对象,则将其忽略。 如果我们在对象列表的范围之外引用,则会抛出运行时异常。
长度部分是可选的。 它是参数的字符串表示形式中的最小字符数。 如果为正,则该参数为右对齐;否则为 0。 如果为负,则为左对齐。 如果指定,则必须用冒号分隔索引和长度。
formatString
是可选的。 它是一个格式化值的字符串,是一种特定的方式。 它可以用来格式化日期,时间,数字或枚举。
在这里,我们展示了如何使用格式项的长度分量。 我们将三列数字打印到终端。 左,中和右对齐。
Program.cs
using System;
namespace Format1
{
class Program
{
static void Main(string[] args)
{
int oranges = 2;
int apples = 4;
int bananas = 3;
string str1 = "There are {0} oranges, {1} apples and {2} bananas";
string str2 = "There are {1} oranges, {2} bananas and {0} apples";
Console.WriteLine(str1, oranges, apples, bananas);
Console.WriteLine(str2, apples, oranges, bananas);
}
}
}
我们向控制台打印一条简单的消息。 我们仅使用格式项的索引部分。
string str1 = "There are {0} oranges, {1} apples and {2} bananas";
{0}
,{1}
和{2}
是格式项。 我们指定索引组件。 其他组件是可选的。
Console.WriteLine(str1, oranges, apples, bananas);
现在,我们将复合格式放在一起。 我们有字符串和对象列表(橙色,苹果,香蕉)。 {0}
格式项目是指橙色。 WriteLine()
方法将{0}
格式项替换为oranges
变量的内容。
string str2 = "There are {1} oranges, {2} bananas and {0} apples";
引用对象的格式项的顺序很重要。
$ dotnet run
There are 2 oranges, 4 apples and 3 bananas
There are 2 oranges, 3 bananas and 4 apples
我们可以看到该程序的结果。
下一个示例将格式化数字数据。
Program.cs
using System;
namespace Format2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("{0} {1, 12}", "Decimal", "Hexadecimal");
Console.WriteLine("{0:D} {1,8:X}", 502, 546);
Console.WriteLine("{0:D} {1,8:X}", 345, 765);
Console.WriteLine("{0:D} {1,8:X}", 320, 654);
Console.WriteLine("{0:D} {1,8:X}", 120, 834);
Console.WriteLine("{0:D} {1,8:X}", 620, 454);
}
}
}
我们以十进制和十六进制格式打印数字。 我们还使用长度分量对齐数字。
Console.WriteLine("{0:D} {1,8:X}", 502, 546);;
{0:D}
格式项指定,将采用提供的对象列表中的第一项并将其格式化为十进制格式。 {1,8:X}
格式项目取第二项。 将其格式化为十六进制格式:X
。 字符串长度为 8 个字符8
。 因为数字只有三个字符,所以它会右对齐并用空字符串填充。
$ dotnet run
Decimal Hexadecimal
502 222
345 2FD
320 28E
120 342
620 1C6
运行示例,我们得到了这个结果。
最后两个示例将格式化数字和日期数据。
Program.cs
using System;
namespace Format3
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(string.Format("Number: {0:N}", 126));
Console.WriteLine(string.Format("Scientific: {0:E}", 126));
Console.WriteLine(string.Format("Currency: {0:C}", 126));
Console.WriteLine(string.Format("Percent: {0:P}", 126));
Console.WriteLine(string.Format("Hexadecimal: {0:X}", 126));
}
}
}
该示例演示了数字的标准格式说明符。 数字 126 以五种不同格式打印:普通,科学,货币,百分比和十六进制。
$ dotnet run
Number: 126.00
Scientific: 1.260000E+002
Currency: $126.00
Percent: 12,600.00%
Hexadecimal: 7E
This is the output of the program.
最后,我们将格式化日期和时间数据。
Program.cs
using System;
namespace Format4
{
class Program
{
static void Main(string[] args)
{
DateTime today = DateTime.Now;
Console.WriteLine(string.Format("Short date: {0:d}", today));
Console.WriteLine(string.Format("Long date: {0:D}", today));
Console.WriteLine(string.Format("Short time: {0:t}", today));
Console.WriteLine(string.Format("Long time: {0:T}", today));
Console.WriteLine(string.Format("Month: {0:M}", today));
Console.WriteLine(string.Format("Year: {0:Y}", today));
}
}
}
该代码示例显示了当前日期和时间的六种不同格式。
$ dotnet run
Short date: 10/24/2019
Long date: Thursday, October 24, 2019
Short time: 1:37 PM
Long time: 1:37:38 PM
Month: October 24
Year: October 2019
这是示例的输出。
C# 教程的这一部分介绍了字符串。
C# 运算符
在 C# 教程的这一部分中,我们将讨论运算符。
表达式是根据操作数和运算符构造的。 表达式的运算符指示将哪些运算应用于操作数。 表达式中运算符的求值顺序由运算符的优先级和关联性确定
运算符是特殊符号,表示已执行某个过程。 编程语言的运算符来自数学。 程序员处理数据。 运算符用于处理数据。 操作数是运算符的输入(参数)之一。
C# 运算符列表
下表显示了 C# 语言中使用的一组运算符。
类别 | 符号 |
---|---|
符号运算符 | + - |
算术 | + - * / % |
逻辑(布尔和按位) | & | ^ ! ~ && || true false |
字符串连接 | + |
递增,递减 | ++ -- |
移位 | << >> |
关系 | == != < > <= >= |
赋值 | = += -= *= /= %= &= |= ^= ??= <<= >>= |
成员访问 | . ?. |
索引 | [] ?[] |
调用 | () |
三元 | ?: |
委托连接和删除 | + - |
对象创建 | new |
类型信息 | as is sizeof typeof |
异常控制 | checked unchecked |
间接地址 | * -> [] & |
Lambda | => |
一个运算符通常有一个或两个操作数。 那些仅使用一个操作数的运算符称为一元运算符。 那些使用两个操作数的对象称为二进制运算符。 还有一个三元运算符?:
,它可以处理三个操作数。
某些运算符可以在不同的上下文中使用。 例如+运算符。 从上表中我们可以看到它在不同情况下使用。 它添加数字,连接字符串或委托; 表示数字的符号。 我们说运算符是重载。
C# 一元运算符
C# 一元运算符包括:+,-,++,-,强制转换运算符()和否定!。
C# 符号运算符
有两个符号运算符:+
和-
。 它们用于指示或更改值的符号。
Program.cs
using System;
namespace MinusSign
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(2);
Console.WriteLine(+2);
Console.WriteLine(-2);
}
}
}
+
和-
符号指示值的符号。 加号可以用来表示我们有一个正数。 可以将其省略,并且通常可以这样做。
Program.cs
using System;
public class MinusSign
{
static void Main()
{
int a = 1;
Console.WriteLine(-a);
Console.WriteLine(-(-a));
}
}
减号更改值的符号。
$ dotnet run
-1
1
这是输出。
C# 增减运算符
将值递增或递减一个是编程中的常见任务。 C# 为此有两个方便的运算符:++
和--
。
x++;
x = x + 1;
...
y--;
y = y - 1;
上面两对表达式的作用相同。
Program.cs
using System;
namespace IncrementDecrement
{
class Program
{
static void Main(string[] args)
{
int x = 6;
x++;
x++;
Console.WriteLine(x);
x--;
Console.WriteLine(x);
}
}
}
在上面的示例中,我们演示了两个运算符的用法。
int x = 6;
x++;
x++;
将x
变量初始化为 6。然后将x
递增两次。 现在变量等于 8。
x--;
我们使用减量运算符。 现在变量等于 7。
$ dotnet run
8
7
C# 显式强制转换运算符
显式强制转换运算符()可用于将一个类型强制转换为另一个类型。 请注意,此运算符仅适用于某些类型。
Program.cs
using System;
namespace CastOperator
{
class Program
{
static void Main(string[] args)
{
float val = 3.2f;
int num = (int) val;
System.Console.WriteLine(num);
}
}
}
在该示例中,我们将float
类型显式转换为int
。
求反运算符
取反运算符(!)反转其操作数的含义。
Program.cs
using System;
namespace Negation
{
class Program
{
static void Main(string[] args)
{
var isValid = false;
if (!isValid)
{
Console.WriteLine("The option is not valid");
}
}
}
}
在该示例中,我们建立了一个否定条件:如果表达式的逆数有效,则执行该条件。
C# 赋值运算符
赋值运算符=
将值赋给变量。 变量是值的占位符。 在数学中,=
运算符具有不同的含义。 在等式中,=
运算符是一个相等运算符。 等式的左侧等于右侧。
int x = 1;
在这里,我们为x
变量分配一个数字。
x = x + 1;
先前的表达式在数学上没有意义。 但这在编程中是合法的。 该表达式将x
变量加 1。 右边等于 2,并且 2 分配给x
。
3 = x;
此代码示例导致语法错误。 我们无法为字面值分配值。
C# 连接字符串
+运算符还用于连接字符串。
Program.cs
using System;
namespace Concatenate
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Return " + "of " + "the king.");
}
}
}
我们使用字符串连接运算符将三个字符串连接在一起。
$ dotnet run
Return of the king.
这是该计划的结果。
C# 算术运算符
下表是 C# 中的算术运算符表。
符号 | 名称 |
---|---|
+ |
加法 |
- |
减法 |
* |
乘法 |
/ |
除法 |
% |
余数 |
以下示例显示了算术运算。
Project.cs
using System;
namespace Arithmetic
{
class Program
{
static void Main(string[] args)
{
int a = 10;
int b = 11;
int c = 12;
int add = a + b + c;
int sb = c - a;
int mult = a * b;
int div = c / 3;
int rem = c % a;
Console.WriteLine(add);
Console.WriteLine(sb);
Console.WriteLine(mult);
Console.WriteLine(div);
Console.WriteLine(rem);
}
}
}
在前面的示例中,我们使用加法,减法,乘法,除法和余数运算。 这些都是数学所熟悉的。
int rem = c % a;
%
运算符称为余数或模运算符。 它找到一个数除以另一个的余数。 例如9 % 4
,9 模 4 为 1,因为 4 两次进入 9 且余数为 1。
$ dotnet run
33
2
110
4
2
这是示例的输出。
接下来,我们将说明整数除法和浮点除法之间的区别。
Program.cs
using System;
namespace Division
{
class Program
{
static void Main(string[] args)
{
int c = 5 / 2;
Console.WriteLine(c);
double d = 5 / 2.0;
Console.WriteLine(d);
}
}
}
在前面的示例中,我们将两个数字相除。
int c = 5 / 2;
Console.WriteLine(c);
在这段代码中,我们完成了整数除法。 除法运算的返回值为整数。 当我们将两个整数相除时,结果是一个整数。
double d = 5 / 2.0;
Console.WriteLine(d);
如果值之一是double
或float
,则执行浮点除法。 在我们的例子中,第二个操作数是双精度数,因此结果是双精度数。
$ dotnet run
2
2.5
我们看到了程序的结果。
C# 布尔运算符
在 C# 中,我们有三个逻辑运算符。 bool
关键字用于声明布尔值。
符号 | 名称 |
---|---|
&& |
逻辑与 |
|| |
逻辑或 |
! |
否定 |
布尔运算符也称为逻辑运算符。
Program.cs
using System;
namespace Boolean
{
class Program
{
static void Main(string[] args)
{
int x = 3;
int y = 8;
Console.WriteLine(x == y);
Console.WriteLine(y > x);
if (y > x)
{
Console.WriteLine("y is greater than x");
}
}
}
}
许多表达式导致布尔值。 布尔值用于条件语句中。
Console.WriteLine(x == y);
Console.WriteLine(y > x);
关系运算符始终导致布尔值。 这两行分别显示false
和true
。
if (y > x)
{
Console.WriteLine("y is greater than x");
}
仅在满足括号内的条件时才执行if
语句的主体。 y > x
返回true
,因此消息"y
大于x"
被打印到终端。
true
和false
关键字表示 C# 中的布尔字面值。
Program.cs
using System;
namespace AndOperator
{
class Program
{
static void Main(string[] args)
{
bool a = true && true;
bool b = true && false;
bool c = false && true;
bool d = false && false;
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(d);
}
}
}
示例显示了逻辑和运算符。 仅当两个操作数均为true
时,它的求值结果为true
。
$ dotnet run
True
False
False
False
True
只产生一个表达式。
如果两个操作数中的任何一个为true
,则逻辑或||
运算符的计算结果为true
。
Program.cs
using System;
namespace OrOperator
{
class Program
{
static void Main(string[] args)
{
bool a = true || true;
bool b = true || false;
bool c = false || true;
bool d = false || false;
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(d);
}
}
}
如果运算符的任一侧为真,则操作的结果为真。
$ dotnet run
True
True
True
False
四个表达式中的三个表示为true
。
否定运算符!
将true
设为false
,并将false
设为false
。
Program.cs
using System;
namespace NegationEx
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(!true);
Console.WriteLine(!false);
Console.WriteLine(!(4 < 3));
}
}
}
该示例显示了否定运算符的作用。
$ dotnet run
False
True
True
这是negation.exe
程序的输出。
||
和&&
运算符经过短路求值。 短路求值意味着仅当第一个参数不足以确定表达式的值时,才求值第二个参数:当逻辑的第一个参数的结果为false
时,总值必须为false
; 当逻辑或的第一个参数为true
时,总值必须为true
。 短路求值主要用于提高性能。
一个例子可以使这一点更加清楚。
Program.cs
using System;
namespace ShortCircuit
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Short circuit");
if (One() && Two())
{
Console.WriteLine("Pass");
}
Console.WriteLine("#############");
if (Two() || One())
{
Console.WriteLine("Pass");
}
}
public static bool One()
{
Console.WriteLine("Inside one");
return false;
}
public static bool Two()
{
Console.WriteLine("Inside two");
return true;
}
}
}
在示例中,我们有两种方法。 它们在布尔表达式中用作操作数。 我们将看看它们是否被调用。
if (One() && Two())
{
Console.WriteLine("Pass");
}
One()
方法返回false
。 短路&&
不求值第二种方法。 没有必要。 一旦操作数为false
,则逻辑结论的结果始终为false
。 仅将"Inside one"
打印到控制台。
Console.WriteLine("#############");
if (Two() || One())
{
Console.WriteLine("Pass");
}
在第二种情况下,我们使用||
运算符,并使用Two()
方法作为第一个操作数。 在这种情况下,"Inside two"
和"Pass"
字符串将打印到终端。 再次不必求值第二操作数,因为一旦第一操作数求值为true
,则逻辑或始终为true
。
$ ShortCircuit>dotnet run
Short circuit
Inside one
#############
Inside two
Pass
We see the result of the program.
C# 关系运算符
关系运算符用于比较值。 这些运算符总是产生布尔值。
符号 | 含义 |
---|---|
< |
小于 |
<= |
小于或等于 |
> |
大于 |
>= |
大于或等于 |
== |
等于 |
!= |
不等于 |
关系运算符也称为比较运算符。
Program.cs
using System;
namespace Relational
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(3 < 4);
Console.WriteLine(3 == 4);
Console.WriteLine(4 >= 3);
Console.WriteLine(4 != 3);
}
}
}
在代码示例中,我们有四个表达式。 这些表达式比较整数值。 每个表达式的结果为true
或false
。 在 C# 中,我们使用==
比较数字。 某些语言(例如 Ada,Visual Basic 或 Pascal)使用=
比较数字。
C# 按位运算符
小数对人类是自然的。 二进制数是计算机固有的。 二进制,八进制,十进制或十六进制符号仅是相同数字的符号。 按位运算符使用二进制数的位。 像 C# 这样的高级语言很少使用按位运算符。
符号 | 含义 |
---|---|
~ |
按位取反 |
^ |
按位异或 |
& |
按位与 |
| |
按位或 |
按位取反运算符分别将 1 更改为 0,将 0 更改为 1。
Console.WriteLine(~ 7); // prints -8
Console.WriteLine(~ -8); // prints 7
运算符恢复数字 7 的所有位。这些位之一还确定数字是否为负。 如果我们再一次对所有位取反,我们将再次得到 7。
按位,运算符在两个数字之间进行逐位比较。 仅当操作数中的两个对应位均为 1 时,位位置的结果才为 1。
00110
& 00011
= 00010
第一个数字是二进制表示法 6,第二个数字是 3,结果是 2。
Console.WriteLine(6 & 3); // prints 2
Console.WriteLine(3 & 6); // prints 2
按位或运算符在两个数字之间进行逐位比较。 如果操作数中的任何对应位为 1,则位位置的结果为 1。
00110
| 00011
= 00111
结果为00110
或十进制 7。
Console.WriteLine(6 | 3); // prints 7
Console.WriteLine(3 | 6); // prints 7
按位互斥或运算符在两个数字之间进行逐位比较。 如果操作数中对应位中的一个或另一个(但不是全部)为 1,则位位置的结果为 1。
00110
^ 00011
= 00101
结果为00101
或十进制 5。
Console.WriteLine(6 ^ 3); // prints 5
Console.WriteLine(3 ^ 6); // prints 5
C# 复合赋值运算符
复合赋值运算符由两个运算符组成。 他们是速记员。
a = a + 3;
a += 3;
+=
复合运算符是这些速记运算符之一。 以上两个表达式相等。 将值 3 添加到变量 a 中。
其他复合运算符是:
-= *= /= %= &= |= <<= >>=
Program.cs
using System;
namespace CompoundOperators
{
class Program
{
static void Main(string[] args)
{
int a = 1;
a = a + 1;
Console.WriteLine(a);
a += 5;
Console.WriteLine(a);
a *= 3;
Console.WriteLine(a);
}
}
}
在示例中,我们使用两个复合运算符。
int a = 1;
a = a + 1;
a
变量被初始化为 1。 使用非速记符号将 1 添加到变量。
a += 5;
使用+=
复合运算符,将 5 加到a
变量中。 该语句等于a = a + 5;
。
a *= 3;
使用*=
运算符,将a
乘以 3。该语句等于a = a * 3;
。
$ dotnet run
2
7
21
这是示例输出。
C# new
运算符
new
运算符用于创建对象和调用构造器。
Program.cs
using System;
namespace NewOperator
{
class Being
{
public Being()
{
Console.WriteLine("Being created");
}
}
class Program
{
static void Main(string[] args)
{
var b = new Being();
Console.WriteLine(b);
var vals = new int[] { 1, 2, 3, 4, 5 };
System.Console.WriteLine(string.Join(" ", vals));
}
}
}
在示例中,我们使用new
运算符创建了一个新的自定义对象和一个整数数组。
public Being()
{
Console.WriteLine("Being created");
}
这是一个构造器。 在创建对象时调用它。
$ dotnet run
Being created
NewOperator.Being
1 2 3 4 5
This is the output.
C# 访问运算符
访问运算符[]
与数组,索引器和属性一起使用。
Program.cs
using System;
using System.Collections.Generic;
namespace AccessOperator
{
class Program
{
static void Main(string[] args)
{
var vals = new int[] { 2, 4, 6, 8, 10 };
Console.WriteLine(vals[0]);
var domains = new Dictionary<string, string>()
{
{ "de", "Germany" },
{ "sk", "Slovakia" },
{ "ru", "Russia" }
};
Console.WriteLine(domains["de"]);
oldMethod();
}
[Obsolete("Don't use OldMethod, use NewMethod instead", false)]
public static void oldMethod()
{
Console.WriteLine("oldMethod()");
}
public static void newMethod()
{
Console.WriteLine("newMethod()");
}
}
}
在该示例中,我们使用[]
运算符获取数组的元素,字典对的值,并激活内置属性。
var vals = new int[] { 2, 4, 6, 8, 10 };
Console.WriteLine(vals[0]);
我们定义一个整数数组。 我们用vals[0]
获得第一个元素。
var domains = new Dictionary<string, string>()
{
{ "de", "Germany" },
{ "sk", "Slovakia" },
{ "ru", "Russia" }
};
Console.WriteLine(domains["de"]);
创建字典。 使用domains["de"]
,我们获得具有"de"
键的货币对的值。
[Obsolete("Don't use OldMethod, use NewMethod instead", false)]
public static void oldMethod()
{
Console.WriteLine("oldMethod()");
}
我们激活了内置的Obsolete
属性。 该属性发出警告。
$ dotnet run
Program.cs(22,13): warning CS0618: 'Program.oldMethod()' is obsolete:
'Don't use OldMethod, use NewMethod instead'
[C:\Users\Jano\Documents\csharp\tutorial\AccessOperator\AccessOperator.csproj]
2
Germany
oldMethod()
This is the output.
C# 索引的最终运算符^
结束运算符^的索引指示从序列结尾开始的元素位置。 例如,^1
指向序列的最后一个元素,^n
指向偏移为length - n
的元素。
Program.cs
using System;
using System.Linq;
namespace IndexFromEnd
{
class Program
{
static void Main(string[] args)
{
int[] vals = { 1, 2, 3, 4, 5 };
Console.WriteLine(vals[^1]);
Console.WriteLine(vals[^2]);
var word = "gray falcon";
Console.WriteLine(word[^1]);
}
}
}
在示例中,我们将运算符应用于数组和字符串。
int[] vals = { 1, 2, 3, 4, 5 };
Console.WriteLine(vals[^1]);
Console.WriteLine(vals[^2]);
我们打印数组的最后一个元素和最后一个元素。
var word = "gray falcon";
Console.WriteLine(word[^1]);
我们打印单词的最后一个字母。
$ dotnet run
5
4
n
This is the output.
C# 范围运算符..
..
运算符指定索引范围的开始和结束作为其操作数。 左侧操作数是范围的一个包含范围的开始。 右侧操作数是范围的排他端。
x.. is equivalent to x..^0
..y is equivalent to 0..y
.. is equivalent to 0..^0
可以省略..
运算符的操作数以获取开放范围。
Program.cs
using System;
namespace RangeOperator
{
class Program
{
static void Main(string[] args)
{
int[] vals = { 1, 2, 3, 4, 5, 6, 7 };
var slice1 = vals[1..4];
Console.WriteLine("[{0}]", String.Join(", ", slice1));
var slice2 = vals[..^0];
Console.WriteLine("[{0}]", String.Join(", ", slice2));
}
}
}
在示例中,我们使用..
运算符获取数组切片。
var range1 = vals[1..4];
Console.WriteLine("[{0}]", String.Join(", ", range1));
我们创建一个从索引 1 到索引 4 的数组切片; 最后一个索引 4 不包括在内。
var slice2 = vals[..^0];
Console.WriteLine("[{0}]", String.Join(", ", slice2));
在这里,我们基本上创建了数组的副本。
$ dotnet run
[2, 3, 4]
[1, 2, 3, 4, 5, 6, 7]
This is the output.
C# 类型信息
现在,我们将关注使用类型的运算符。
sizeof
运算符用于获取值类型的字节大小。 typeof
用于获取类型的System.Type
对象。
Program.cs
using System;
namespace SizeType
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(sizeof(int));
Console.WriteLine(sizeof(float));
Console.WriteLine(sizeof(Int32));
Console.WriteLine(typeof(int));
Console.WriteLine(typeof(float));
}
}
}
我们使用sizeof
和typeof
运算符。
$ dotnet run
4
4
4
System.Int32
我们可以看到int
类型是System.Int32
的别名,float
是System.Single
类型的别名。
is
操作符检查对象是否与给定类型兼容。
Program.cs
using System;
namespace IsOperator
{
class Base { }
class Derived : Base { }
class Program
{
static void Main(string[] args)
{
Base _base = new Base();
Derived derived = new Derived();
Console.WriteLine(_base is Base);
Console.WriteLine(_base is Object);
Console.WriteLine(derived is Base);
Console.WriteLine(_base is Derived);
}
}
}
我们根据用户定义的类型创建两个对象。
class Base {}
class Derived : Base {}
我们有一个Base
和Derived
类。 Derived
类继承自Base
类。
Console.WriteLine(_base is Base);
Console.WriteLine(_base is Object);
Base
等于Base
,因此第一行显示True
。 Base
也与Object
类型兼容。 这是因为每个类都继承自所有类的母体Object
类。
Console.WriteLine(derived is Base);
Console.WriteLine(_base is Derived);
派生对象与Base
类兼容,因为它显式继承自Base
类。 另一方面,_base
对象与Derived
类无关。
$ dotnet run
True
True
True
False
这是示例的输出。
as
运算符用于在兼容的引用类型之间执行转换。 如果无法进行转换,则运算符将返回null
。 与强制转换操作不同,后者引发异常。
Program.cs
using System;
namespace AsOperator
{
class Base { }
class Derived : Base { }
class Program
{
static void Main(string[] args)
{
object[] objects = new object[6];
objects[0] = new Base();
objects[1] = new Derived();
objects[2] = "ZetCode";
objects[3] = 12;
objects[4] = 1.4;
objects[5] = null;
for (int i = 0; i < objects.Length; i++)
{
string s = objects[i] as string;
Console.Write("{0}:", i);
if (s != null)
{
Console.WriteLine(s);
}
else
{
Console.WriteLine("not a string");
}
}
}
}
}
在上面的示例中,我们使用as
运算符执行转换。
string s = objects[i] as string;
我们尝试将各种类型转换为字符串类型。 但只有一次转换有效。
$ dotnet run
0:not a string
1:not a string
2:ZetCode
3:not a string
4:not a string
5:not a string
This is the output of the example.
C# 运算符优先级
运算符优先级告诉我们首先求值哪个运算符。 优先级对于避免表达式中的歧义是必要的。
以下表达式 28 或 40 的结果是什么?
3 + 5 * 5
像数学中一样,乘法运算符的优先级高于加法运算符。 结果是 28。
(3 + 5) * 5
要更改求值的顺序,可以使用括号。 括号内的表达式始终首先被求值。
下表显示了按优先级排序的通用 C# 运算符(优先级最高):
运算符 | 类别 | 关联性 |
---|---|---|
主要 | x.y x?.y, x?[y] f(x) a[x] x++ x-- new typeof default checked unchecked |
左 |
一元 | + - ! ~ ++x --x (T)x |
左 |
乘法 | * / % |
左 |
加法 | + - |
左 |
移位 | << >> |
左 |
相等 | == != |
右 |
逻辑与 | & |
左 |
逻辑异或 | ^ |
左 |
逻辑或 | | |
左 |
条件与 | && |
左 |
条件或 | || |
左 |
空合并 | ?? |
左 |
三元 | ?: |
右 |
赋值 | = *= /= %= += -= <<= >>= &= ^= |= ??= => |
右 |
表的同一行上的运算符具有相同的优先级。
Program.cs
using System;
namespace Precedence
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(3 + 5 * 5);
Console.WriteLine((3 + 5) * 5);
Console.WriteLine(! true | true);
Console.WriteLine(! (true | true));
}
}
}
在此代码示例中,我们显示一些表达式。 每个表达式的结果取决于优先级。
Console.WriteLine(3 + 5 * 5);
该行打印 28。乘法运算符的优先级高于加法。 首先,计算5*5
的乘积,然后加 3。
Console.WriteLine(! true | true);
在这种情况下,否定运算符具有更高的优先级。 首先,将第一个true
值取反为false
,然后|
运算符将false
和true
组合在一起,最后给出true
。
$ dotnet run
28
40
True
False
这是precedence.exe
程序的结果。
C# 关联规则
有时,优先级不能令人满意地确定表达式的结果。 还有另一个规则称为关联性。 运算符的关联性确定优先级与相同的运算符的求值顺序。
9 / 3 * 3
此表达式的结果是 9 还是 1? 乘法,删除和模运算符从左到右关联。 因此,该表达式的计算方式为:(9 / 3) * 3
,结果为 9。
算术,布尔,关系和按位运算符都是从左到右关联的。
另一方面,赋值运算符是正确关联的。
Program.cs
using System;
namespace Associativity
{
class Program
{
static void Main(string[] args)
{
int a, b, c, d;
a = b = c = d = 0;
Console.WriteLine("{0} {1} {2} {3}", a, b, c, d);
int j = 0;
j *= 3 + 1;
Console.WriteLine(j);
}
}
}
在该示例中,有两种情况,其中关联性规则确定表达式。
int a, b, c, d;
a = b = c = d = 0;
赋值运算符从右到左关联。 如果关联性从左到右,则以前的表达式将不可能。
int j = 0;
j *= 3 + 1;
复合赋值运算符从右到左关联。 我们可能期望结果为 1。但是实际结果为 0。由于有关联性。 首先求值右边的表达式,然后应用复合赋值运算符。
$ dotnet run
0 0 0 0
0
This is the output.
C# 空条件运算符
空条件运算符仅在该操作数的值为非空时才将成员访问?.
或元素访问 ?[]
应用于其操作数。 如果操作数的值为null
,则应用运算符的结果为null
。
Program.cs
using System;
using System.Collections.Generic;
namespace NullConditional
{
class User
{
public User() { }
public User(string name, string occupation)
{
this.name = name;
this.occupation = occupation;
}
public string name { get; set; }
public string occupation { get; set; }
public override string ToString() => $"{name} {occupation}";
}
class Program
{
static void Main(string[] args)
{
var users = new List<User>() { new User("John Doe", "gardener"), new User(),
new User("Lucia Newton", "teacher") };
users.ForEach(user => Console.WriteLine(user.name?.ToUpper()));
}
}
}
在示例中,我们有一个带有两个成员的User
类:name
和occupation
。 我们在?.
运算符的帮助下访问对象的name
成员。
var users = new List<User>() { new User("John Doe", "gardener"), new User(),
new User("Lucia Newton", "teacher") };
我们有一个用户列表。 其中一个未初始化,因此其成员为null
。
users.ForEach(user => Console.WriteLine(user.name?.ToUpper()));
我们使用?.
访问name
成员并调用ToUpper()
方法。 ?.
通过不调用null
值上的ToUpper()
来防止System.NullReferenceException
。
$ dotnet run
JOHN DOE
LUCIA NEWTON
This is the output.
在下面的示例中,我们使用[].
运算符。
Program.cs
using System;
namespace NullConditional2
{
class Program
{
static void Main(string[] args)
{
int?[] vals = { 1, 2, 3, null, 4, 5 };
int i = 0;
while (i < vals.Length)
{
Console.WriteLine(vals[i]?.GetType());
i++;
}
}
}
}
在此示例中,我们在数组中有一个null
值。 我们通过在数组元素上应用[].
运算符来防止System.NullReferenceException
。
C# 空值运算符
空合并运算符??
用于定义nullable
类型的默认值。 如果不为null
,则返回左侧操作数;否则返回 0。 否则返回正确的操作数。 当我们使用数据库时,我们经常处理缺失的值。 这些值在程序中为空。 该运算符是处理此类情况的便捷方法。
Program.cs
using System;
namespace NullCoalescing
{
class Program
{
static void Main(string[] args)
{
int? x = null;
int? y = null;
int z = x ?? y ?? -1;
Console.WriteLine(z);
}
}
}
空合并运算符的示例程序。
int? x = null;
int? y = null;
两种可为空的int
类型被初始化为null
。 int?
是Nullable<int>
的简写。 它允许将空值分配给int
类型。
int z = x ?? y ?? -1;
我们要为z
变量分配一个值。 但是它一定不是null
。 这是我们的要求。 我们可以轻松地为此使用null
折叠运算符。 如果x
和y
变量均为空,我们将 -1 分配给z
。
$ dotnet run
-1
这是程序的输出。
C# 空折叠赋值运算符
仅当左侧操作数的值为null
时,空合并赋值运算符??=
才将其右侧操作数的值分配给其左侧操作数。 如果??=
运算符的左手操作数取值为非空,则不计算其右手操作数。 它在 C# 8.0 和更高版本中可用。
Program.cs
using System;
using System.Collections.Generic;
namespace NullCoalescingAssignment
{
class Program
{
static void Main(string[] args)
{
List<int> vals = null;
vals ??= new List<int>() {1, 2, 3, 4, 5, 6};
vals.Add(7);
vals.Add(8);
vals.Add(9);
Console.WriteLine(string.Join(", ", vals));
vals ??= new List<int>() {1, 2, 3, 4, 5, 6};
Console.WriteLine(string.Join(", ", vals));
}
}
}
在该示例中,我们在整数值列表上使用null
折叠赋值运算符。
List<int> vals = null;
首先,将列表分配给null
。
vals ??= new List<int>() {1, 2, 3, 4, 5, 6};
我们使用??=
将新的列表对象分配给变量。 由于它是null
,因此分配了列表。
vals.Add(7);
vals.Add(8);
vals.Add(9);
Console.WriteLine(string.Join(", ", vals));
我们将一些值添加到列表中并打印其内容。
vals ??= new List<int>() {1, 2, 3, 4, 5, 6};
我们尝试为变量分配一个新的列表对象。 由于该变量不再是null
,因此不会分配该列表。
$ dotnet run
1, 2, 3, 4, 5, 6, 7, 8, 9
1, 2, 3, 4, 5, 6, 7, 8, 9
This is the output.
C# 三元运算符
三元运算符?:
是条件运算符。 对于要根据条件表达式选择两个值之一的情况,它是一个方便的运算符。
cond-exp ? exp1 : exp2
如果cond-exp
为true
,则求值exp1
并返回结果。 如果cond-exp
为false
,则求值exp2
并返回其结果。
Program.cs
using System;
namespace Ternary
{
class Program
{
static void Main(string[] args)
{
int age = 31;
bool adult = age >= 18 ? true : false;
Console.WriteLine("Adult: {0}", adult);
}
}
}
在大多数国家/地区,成年取决于您的年龄。 如果您的年龄超过特定年龄,则您已经成年。 对于三元运算符,这是一种情况。
bool adult = age >= 18 ? true : false;
首先,对赋值运算符右侧的表达式进行求值。 三元运算符的第一阶段是条件表达式求值。 因此,如果年龄大于或等于 18,则返回?
字符后的值。 如果不是,则返回:
字符后的值。 然后将返回值分配给成人变量。
$ dotnet run
Adult: True
31 岁的成年人是成年人。
C# Lambda 运算符
=>
令牌称为 lambda 运算符。 它是从函数式语言中提取的运算符。 该运算符可以使代码更短,更清晰。 另一方面,理解语法可能很棘手。 特别是如果程序员以前从未使用过函数式语言。
只要可以使用委托,我们都可以使用 lambda 表达式。 lambda 表达式的定义是:lambda 表达式是一个匿名函数,可以包含表达式和语句。 左边是一组数据,右边是表达式或语句块。 这些语句应用于数据的每个项目。
在 lambda 表达式中,我们没有return
关键字。 最后一条语句自动返回。 而且,我们不需要为参数指定类型。 编译器将猜测正确的参数类型。 这称为类型推断。
Program.cs
using System;
using System.Collections.Generic;
namespace LambdaOperator
{
class Program
{
static void Main(string[] args)
{
var list = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 };
var subList = list.FindAll(val => val > 3);
foreach (int i in subList)
{
Console.WriteLine(i);
}
}
}
}
我们有一个整数列表。 我们打印所有大于 3 的数字。
var list = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 };
我们有一个通用的整数列表。
var subList = list.FindAll(val => val > 3);
在这里,我们使用 lambda 运算符。 FindAll()
方法采用谓词作为参数。 谓词是一种特殊的委托,它返回布尔值。 该谓词适用于列表中的所有项目。 val
是没有类型指定的输入参数。 我们可以明确指定类型,但这不是必需的。 编译器将期望使用int
类型。 val
是列表中的当前输入值。 比较它是否大于 3 并返回布尔值true
或false
。 最后,FindAll()
将返回所有符合条件的值。 它们被分配给子列表集合。
foreach (int i in subList)
{
Console.WriteLine(i);
}
子列表集合的项目将打印到终端。
$ dotnet run
8
6
4
7
9
5
大于 3 的整数列表中的值。
Program.cs
using System;
using System.Collections.Generic;
namespace AnonymousDelegate
{
class Program
{
static void Main(string[] args)
{
var nums = new List<int>() { 3, 2, 1, 8, 6, 4, 7, 9, 5 };
var nums2 = nums.FindAll( delegate(int i) {
return i > 3;
}
);
foreach (int i in nums2)
{
Console.WriteLine(i);
}
}
}
}
这是相同的例子。 我们使用匿名委托代替 lambda 表达式。
C# 计算素数
我们将计算素数。
Program.cs
using System;
namespace Primes
{
class Program
{
static void Main(string[] args)
{
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28 };
Console.Write("Prime numbers: ");
foreach (int num in nums)
{
if (num == 1) continue;
if (num == 2 || num == 3)
{
Console.Write(num + " ");
continue;
}
int i = (int) Math.Sqrt(num);
bool isPrime = true;
while (i > 1)
{
if (num % i == 0)
{
isPrime = false;
}
i--;
}
if (isPrime)
{
Console.Write(num + " ");
}
}
Console.Write('\n');
}
}
}
在上面的示例中,我们处理了许多不同的运算符。 质数(或质数)是一个自然数,它具有两个截然不同的自然数除数:1 和它本身。 我们拾取一个数字并将其除以数字,从 1 到拾取的数字。 实际上,我们不必尝试所有较小的数字。 我们可以将数字除以所选数字的平方根。 该公式将起作用。 我们使用余数除法运算符。
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28 };
我们将从这些数字计算素数。
if (num == 1) continue;
根据定义,1 不是质数
if (num == 2 || num == 3)
{
Console.Write(num + " ");
continue;
}
我们跳过 2 和 3 的计算,它们是质数。 请注意等式和条件或运算符的用法。 ==
的优先级高于||
运算符。 因此,我们不需要使用括号。
int i = (int) Math.Sqrt(num);
如果我们仅尝试小于所讨论数字的平方根的数字,那么我们可以。
while (i > 1)
{
...
i--;
}
这是一个while
循环。 i
是计算出的数字的平方根。 我们使用减量运算符将每个循环周期的i
减 1。 当i
小于 1 时,我们终止循环。 例如,我们有 9。9 的平方根是 3。我们将 9 的数字除以 3 和 2。这对于我们的计算就足够了。
if (num % i == 0)
{
isPrime = false;
}
这是算法的核心。 如果余数除法运算符对于任何i
值返回 0,则说明所讨论的数字不是质数。
在 C# 教程的这一部分中,我们介绍了 C# 运算符。
C# 中的流控制
在 C# 教程的这一部分中,将讨论流控制。 我们定义了几个关键字,这些关键字使我们能够控制 C# 程序的流程。
在 C# 语言中,有几个关键字可用于更改程序的流程。 当程序运行时,语句从源文件的顶部到底部执行。 逐一。 可以通过特定的关键字更改此流程。 语句可以执行多次。 一些语句称为条件语句。 仅在满足特定条件时才执行它们。
C# if
语句
if
语句具有以下一般形式:
if (expression)
{
statement;
}
if
关键字用于检查表达式是否为真。 如果为true
,则执行一条语句。 该语句可以是单个语句或复合语句。 复合语句由该块包围的多个语句组成。 块是用大括号括起来的代码。
Program.cs
using System;
namespace IfStatement
{
class Program
{
static void Main(string[] args)
{
var r = new Random();
int n = r.Next(-5, 5);
Console.WriteLine(n);
if (n > 0)
{
Console.WriteLine("The n variable is positive");
}
}
}
}
生成一个随机数。 如果数字大于零,我们将向终端打印一条消息。
var r = new Random();
int n = r.Next(-5, 5);
这两行生成 -5, 5 之间的随机整数。
if (n > 0)
{
Console.WriteLine("The n variable is positive");
}
使用if
关键字,我们检查生成的数字是否大于零。 if
关键字后跟一对圆括号。 在方括号内,我们放置一个表达式。 该表达式产生布尔值。 如果布尔值是true
,则执行两个大括号括起来的块。 在我们的例子中,字符串The n variable is positive
被打印到终端上。 如果随机值为负,则不执行任何操作。 如果我们只有一个表达式,则大括号是可选的。
$ dotnet run
-3
$ dotnet run
-4
$ dotnet run
-1
$ dotnet run
1
The n variable is positive
满足条件后,消息将写入控制台。
C# else
语句
我们可以使用else
关键字来创建一个简单的分支。 如果if
关键字后方括号内的表达式的值为假,则将自动执行else
关键字后方的语句。
Program.cs
using System;
namespace IfElse
{
class Program
{
static void Main(string[] args)
{
var r = new Random();
int n = r.Next(-5, 5);
Console.WriteLine(n);
if (n > 0)
{
Console.WriteLine("The number is positive");
} else
{
Console.WriteLine("The number is negative");
}
}
}
}
要么执行if
关键字之后的块,要么执行else
关键字之后的块。
if (n > 0)
{
Console.WriteLine("The number is positive");
} else
{
Console.WriteLine("The number is negative");
}
else
关键字紧随if
块的右大括号。 它有自己的块,用大括号括起来。
$ dotnet run
-3
The number is negative
$ dotnet run
-1
The number is negative
$ dotnet run
2
The number is positive
我们执行该程序三次。
C# else if
我们可以使用else if
关键字创建多个分支。 仅当不满足先前条件时,else if
关键字才会测试其他条件。 请注意,我们可以在测试中使用多个else if
关键字。
Program.cs
using System;
namespace MultipleBranches
{
class Program
{
static void Main(string[] args)
{
var r = new Random();
int n = r.Next(-5, 5);
Console.WriteLine(n);
if (n < 0)
{
Console.WriteLine("The n variable is negative");
} else if (n == 0)
{
Console.WriteLine("The n variable is zero");
} else
{
Console.WriteLine("The n variable is positive");
}
}
}
}
以前的程序有一个小问题。 负值设为零。 以下程序将解决此问题。
if (n < 0)
{
Console.WriteLine("The n variable is negative");
} else if (n == 0)
{
Console.WriteLine("The n variable is zero");
} else
{
Console.WriteLine("The n variable is positive");
}
如果第一个条件的计算结果为true
,例如输入的值小于零,将执行第一个程序段,并跳过其余两个程序段。 如果不满足第一个条件,则检查if else
关键字之后的第二个条件。 如果第二个条件的值为真,则执行第二个块。 如果不是,则执行else
关键字之后的第三个程序段。 如果不满足先前的条件,则始终执行else
块。
$ dotnet run
-1
The n variable is negative
$ dotnet run
4
The n variable is positive
$ dotnet run
1
The n variable is positive
$ dotnet run
0
The n variable is zero
我们执行该程序三次。 0 被正确处理。
C# switch
语句
switch
语句是选择控制流语句。 它允许变量或表达式的值通过多路分支控制程序执行的流程。 与使用if
/ else if
/ else
语句的组合相比,它以更简单的方式创建多个分支。
我们有一个变量或一个表达式。 switch
关键字用于根据值列表测试变量或表达式中的值。 值列表用case
关键字显示。 如果值匹配,则执行case
之后的语句。 有一个可选的default
语句。 如果找不到其他匹配项,则执行该命令。
从 C# 7.0 开始,匹配表达式可以是任何非null
表达式。
switch
语句非常复杂。
选择星期几
在下面的示例中,我们使用switch
语句选择星期几。
Program.cs
using System;
namespace SwitchDayOfWeek
{
class Program
{
static void Main(string[] args)
{
var dayOfWeek = DateTime.Now.DayOfWeek;
switch (dayOfWeek)
{
case DayOfWeek.Sunday:
Console.WriteLine("dies Solis");
break;
case DayOfWeek.Monday:
Console.WriteLine("dies Lunae");
break;
case DayOfWeek.Tuesday:
Console.WriteLine("dies Martis");
break;
case DayOfWeek.Wednesday:
Console.WriteLine("dies Mercurii");
break;
case DayOfWeek.Thursday:
Console.WriteLine("dies Jovis");
break;
case DayOfWeek.Friday:
Console.WriteLine("dies Veneris");
break;
case DayOfWeek.Saturday:
Console.WriteLine("dies Saturni");
break;
}
}
}
}
该示例确定星期几并打印其等效的拉丁语。
switch (dayOfWeek)
{
...
}
在圆括号中,switch
关键字从要测试的表达式中获取一个值。 switch
关键字的主体放在一对或大括号内。 在体内,我们可以放置多个case
选项。 每个选项都以break
关键字结尾。
case DayOfWeek.Sunday:
Console.WriteLine("dies Solis");
break;
使用case
语句,我们测试匹配表达式的值。 如果它等于DayOfWeek.Sunday
,则打印拉丁语dies Solis
。
$ dotnet run
dies Solis
该程序在周日运行。
选择区域
要求用户输入域名。 读取域名并将其存储在变量中。 该变量使用switch
关键字针对选项列表进行测试。
Program.cs
using System;
namespace SwitchStatement
{
class Program
{
static void Main(string[] args)
{
Console.Write("Enter a domain name: ");
string domain = Console.ReadLine();
domain = domain.Trim().ToLower();
switch (domain)
{
case "us":
Console.WriteLine("United States");
break;
case "de":
Console.WriteLine("Germany");
break;
case "sk":
Console.WriteLine("Slovakia");
break;
case "hu":
Console.WriteLine("Hungary");
break;
default:
Console.WriteLine("Unknown");
break;
}
}
}
}
在我们的程序中,我们有一个domain
变量。 我们从命令行读取变量的值。 我们使用case
语句测试变量的值。 有几种选择。 例如,如果该值等于"us"
,则将"United States"
字符串打印到控制台。
string domain = Console.ReadLine();
从控制台读取用户输入。
domain = domain.Trim().ToLower();
Trim()
方法从潜在的前导和尾随空白中剥离变量。 ToLower()
将字符转换为小写。 现在,"us"
,"US"
,"us"
是美国域名的可行选项。
case "us":
Console.WriteLine("United States");
break;
在这种情况下,我们测试域变量是否等于"us"
字符串。 如果为true
,则将消息打印到控制台。 该选项以break
关键字结束。 如果成功求值了其中一个选项,则break
关键字将终止switch
块。
default:
Console.WriteLine("Unknown");
break;
default
关键字是可选的。 如果没有求值case
选项,则执行default
部分。
$ dotnet run
Enter a domain name: us
United States
$ dotnet run
Enter a domain name: HU
Hungary
$ dotnet run
Enter a domain name: pl
Unknown
我们执行该程序几次。
使用when
语句
case
语句可与when
语句一起使用以指定其他条件。
Program.cs
using System;
namespace SwitchWhen
{
class Program
{
static void Main(string[] args)
{
Console.Write("Enter your age: ");
var input = Console.ReadLine();
var age = Int32.Parse(input.Trim());
switch (age)
{
case var myAge when myAge < 0:
Console.WriteLine("Age cannot be a negative value");
break;
case var myAge when myAge > 130:
Console.WriteLine("This is an unlikely high age");
break;
default:
Console.WriteLine("The entered age is {0}", age);
break;
}
}
}
}
在程序中,要求用户输入他的年龄。
case var myAge when myAge < 0:
Console.WriteLine("Age cannot be a negative value");
break;
借助when
表达式,我们测试输入的值是否小于 0。使用var
关键字,我们创建一个临时的myAge
变量。
使用枚举作为匹配表达式
对于匹配的表达式,我们可以使用任何类型。 在下面的示例中,我们使用枚举。
Program.cs
using System;
namespace SwitchEnum
{
enum Color { Red, Green, Blue, Brown, Yellow, Pink, Orange }
class Program
{
static void Main(string[] args)
{
var color = (Color) (new Random()).Next(0, 7);
switch (color)
{
case Color.Red:
Console.WriteLine("The color is red");
break;
case Color.Green:
Console.WriteLine("The color is green");
break;
case Color.Blue:
Console.WriteLine("The color is blue");
break;
case Color.Brown:
Console.WriteLine("The color is brown");
break;
case Color.Yellow:
Console.WriteLine("The color is yellow");
break;
case Color.Pink:
Console.WriteLine("The color is pink");
break;
case Color.Orange:
Console.WriteLine("The color is orange");
break;
default:
Console.WriteLine("The color is unknown.");
break;
}
}
}
}
该示例随机生成颜色枚举。 swith
语句确定生成哪个颜色值。
$ dotnet run
The color is orange
$ dotnet run
The color is blue
$ dotnet run
The color is brown
我们运行程序。
switch
表达式
switch
表达式使switch
语句的语法更加简洁。 它们是在 C# 8.0 中引入的。
Program.cs
using System;
namespace SwitchExpressions
{
class Program
{
static void Main(string[] args)
{
Console.Write("Enter a domain name: ");
string domain = Console.ReadLine();
domain = domain.Trim().ToLower();
string result = domain switch
{
"us" => "United States",
"de" => "Germany",
"sk" => "Slovakia",
"hu" => "Hungary",
_ => "Unknown"
};
Console.WriteLine(result);
}
}
}
该变量位于switch
关键字之前。 case
和:
元素替换为=>
。 default
案例被_
丢弃替换。 主体是表达式,而不是语句。
C# while
语句
while
语句是一个控制流语句,它允许根据给定的布尔条件重复执行代码。
这是while
循环的一般形式:
while (expression)
{
statement;
}
while
关键字在大括号括起来的块内执行语句。 每次将表达式求值为true
时都会执行这些语句。
Program.cs
using System;
namespace WhileStatement
{
class Program
{
static void Main(string[] args)
{
int i = 0;
int sum = 0;
while (i < 10)
{
i++;
sum += i;
}
Console.WriteLine(sum);
}
}
}
在代码示例中,我们从一系列数字计算值的总和。
while
循环分为三个部分。 初始化,测试和更新。 语句的每次执行都称为循环。
int i = 0;
我们启动i
变量。 它用作计数器。
while (i < 10)
{
...
}
while
关键字后的圆括号内的表达式是第二阶段,即测试。 执行主体中的语句,直到表达式的计算结果为false
。
i++;
这是while
循环的最后一个第三阶段,即更新。 我们增加计数器。 请注意,对while
循环的不正确处理可能会导致循环不断。
do while
语句
可以至少运行一次该语句。 即使不满足条件。 为此,我们可以使用do while
关键字。
Program.cs
using System;
namespace DoWhile
{
class Program
{
static void Main(string[] args)
{
int count = 0;
do {
Console.WriteLine(count);
} while (count != 0);
}
}
}
首先执行该块,然后求值真值表达式。 在我们的情况下,条件不满足,do while
语句终止。
C# 循环语句
如果在启动循环之前知道周期数,则可以使用for
语句。 在此构造中,我们声明一个计数器变量,该变量在每次循环重复期间会自动增加或减少值。
简单的循环
for
循环分为三个阶段:初始化,条件和代码块执行以及递增。
Program.cs
using System;
namespace ForStatement
{
class Program
{
static void Main(string[] args)
{
for (int i=0; i<10; i++)
{
Console.WriteLine(i);
}
}
}
}
在此示例中,我们将数字0..9
打印到控制台。
for (int i=0; i<9; i++)
{
Console.WriteLine(i);
}
分为三个阶段。 在第一阶段,我们将计数器i
初始化为零。 此阶段仅完成一次。 接下来是条件。 如果满足条件,则执行for
块中的语句。 在第三阶段,计数器增加。 现在,我们重复 2、3 个阶段,直到不满足条件并保留for
循环为止。 在我们的情况下,当计数器i
等于 10 时,for
循环停止执行。
for
循环数组遍历
for
循环可用于遍历数组。 从数组的Length
属性,我们知道数组的大小。
Program.cs
using System;
namespace ForStatement2
{
class Program
{
static void Main(string[] args)
{
string[] planets = { "Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn", "Uranus", "Pluto" };
for (int i = 0; i < planets.Length; i++)
{
Console.WriteLine(planets[i]);
}
Console.WriteLine("In reverse:");
for (int i = planets.Length - 1; i >= 0; i--)
{
Console.WriteLine(planets[i]);
}
}
}
}
我们有一个数组,用于保存太阳系中行星的名称。 使用两个for
循环,我们按升序和降序打印值。
for (int i = 0; i < planets.Length; i++)
{
Console.WriteLine(planets[i]);
}
通过从零开始的索引访问数组。 第一项的索引为 0。因此,i
变量被初始化为零。 条件检查i
变量是否小于数组的长度。 在最后阶段,i
变量增加。
for (int i = planets.Length - 1; i >= 0; i--)
{
Console.WriteLine(planets[i]);
}
此for
循环以相反顺序打印数组的元素。 i
计数器被初始化为数组大小。 由于索引基于零,因此最后一个元素的索引数组大小为 1。 该条件确保计数器大于或等于零。 (数组索引不能为负数)。 在第三步中,i
计数器递减 1。
for
循环中的更多表达式
可以在for
循环的初始化和迭代阶段中放置更多表达式。
Program.cs
using System;
namespace ForStatement3
{
class Program
{
static void Main(string[] args)
{
var r = new Random();
var values = new int[10];
int sum = 0;
int num = 0;
for (int i = 0; i < 10; i++, sum += num)
{
num = r.Next(10);
values[i] = num;
}
Console.WriteLine(string.Join(",", values));
Console.WriteLine("The sum of the values is {0}", sum);
}
}
}
在我们的示例中,我们创建了一个十个随机数的数组。 计算这些数字的总和。
for (int i = 0; i < 10; i++, sum += num)
{
num = r.Next(10);
values[i] = num;
}
在for
循环的第三部分中,我们有两个用逗号分隔的表达式。 i
计数器增加,并且当前编号添加到sum
变量中。
Console.WriteLine(string.Join(",", values));
使用System.String
类的Join()
方法,一次打印出数组的所有值。 它们将以逗号分隔。
$ dotnet run
9,3,1,7,9,8,5,6,3,3
The sum of the values is 54
We run the program.
嵌套循环
For
语句可以嵌套; 即一个for
语句可以放在另一个for
语句中。 嵌套for
循环的所有循环都针对外部for
循环的每个循环执行。
Program.cs
using System;
namespace NestedForLoops
{
class Program
{
static void Main(string[] args)
{
var a1 = new string[] { "A", "B", "C" };
var a2 = new string[] { "A", "B", "C" };
for (int i=0; i<a1.Length; i++)
{
for (int j=0; j<a2.Length; j++)
{
Console.WriteLine(a1[i] + a2[j]);
}
}
}
}
}
在此示例中,我们创建了两个数组的笛卡尔乘积。
var a1 = new string[] { "A", "B", "C" };
var a2 = new string[] { "A", "B", "C" };
我们有两个数组。 每个数组都有树字母。 凯氏积是当一个数组中的每个元素与另一数组中的所有元素配对时。 为此,我们使用嵌套的for
循环。
for (int i=0; i<a1.Length; i++)
{
for (int j=0; j<a2.Length; j++)
{
Console.WriteLine(a1[i] + a2[j]);
}
}
在另一个父 for 循环中有一个嵌套的 for 循环。 嵌套的 for 循环在父 for 循环的每个循环中完全执行。
$ dotnet run
AA
AB
AC
BA
BB
BC
CA
CB
CC
这是输出。
C# foreach
语句
foreach
构造简化了遍历数据集合的过程。 它没有明确的计数器。 foreach
语句一个接一个地遍历数组或集合,并将当前值复制到构造中定义的变量中。
Program.cs
using System;
namespace ForeachStatement
{
class Program
{
static void Main(string[] args)
{
string[] planets = { "Mercury", "Venus",
"Earth", "Mars", "Jupiter", "Saturn",
"Uranus", "Neptune" };
foreach (string planet in planets)
{
Console.WriteLine(planet);
}
}
}
}
在此示例中,我们使用foreach
语句遍历一系列行星。
foreach (string planet in planets)
{
Console.WriteLine(planet);
}
foreach
语句的用法很简单。 planets
是我们迭代通过的数组。 planet
是一个临时变量,具有数组中的当前值。 foreach
语句遍历所有行星并将它们打印到控制台。
$ dotnet run
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune
运行程序将给出此输出。
C# break
语句
break
语句可用于终止由while
,for
或switch
语句定义的块。
Program.cs
using System;
namespace BreakStatement
{
class Program
{
static void Main(string[] args)
{
var random = new Random();
while (true)
{
int num = random.Next(1, 30);
Console.Write("{0} ", num);
if (num == 22)
{
break;
}
}
Console.Write('\n');
}
}
}
我们定义了一个无限的while
循环。 我们使用break
语句退出此循环。 我们从 1 到 30 中选择一个随机值。我们打印该值。 如果该值等于 22,则结束无穷的while
循环。
$ dotnet run
18 3 21 26 12 27 23 25 2 21 15 4 18 12 24 13 7 19 10 26 5 22
我们可能会得到这样的东西。
C# continue
语句
continue
语句用于跳过循环的一部分,并继续循环的下一个迭代。 它可以与for
和while
语句结合使用。
在下面的示例中,我们将打印一个数字列表,这些数字不能除以 2 而没有余数。
Program.cs
using System;
namespace ContinueStatement
{
class Program
{
static void Main(string[] args)
{
int num = 0;
while (num < 1000)
{
num++;
if ((num % 2) == 0)
{
continue;
}
Console.Write("{0} ", num);
}
Console.Write('\n');
}
}
}
我们使用while
循环遍历数字1..999
。
if ((num % 2) == 0)
{
continue;
}
如果表达式num % 2
返回 0,则可以将所讨论的数字除以 2。执行continue
语句,并跳过循环的其余部分。 在我们的例子中,循环的最后一条语句将被跳过,并且数字不会输出到控制台。 下一个迭代开始。
在 C# 教程的这一部分中,我们介绍了 C# 控件流结构。
{% raw %}
C# 数组
在 C# 编程教程的这一部分中,我们将介绍数组。 我们将初始化数组并从中读取数据。
C# 数组定义
数组是数据的集合。 标量变量一次只能容纳一项。 数组可以容纳多个项目。 这些项目称为数组的元素。 数组存储相同数据类型的数据。 每个元素都可以由索引引用。 数组从零开始。 第一个元素的索引为零。 数组是引用类型。
数组用于存储我们应用的数据。 我们声明数组为某种数据类型。 我们指定它们的长度。 我们用数据初始化数组。 我们有几种使用数组的方法。 我们可以修改元素,对其进行排序,复制或搜索。
int[] ages;
String[] names;
float[] weights;
我们有三个数组声明。 声明由两部分组成。 数组的类型和名称。 数组的类型具有确定数组中元素的类型(在我们的情况下为int
,String
,float
)和一对方括号[]
的数据类型。 方括号表示我们有一个数组。
集合具有相似的目的。 它们比数组更强大。 稍后将在单独的章节中进行介绍。
C# 初始化数组
有几种方法,如何在 C# 中初始化数组。
Program.cs
using System;
namespace InitArray
{
class Program
{
static void Main(string[] args)
{
int[] array = new int[5];
array[0] = 1;
array[1] = 2;
array[2] = 3;
array[3] = 4;
array[4] = 5;
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
}
}
}
我们声明并初始化一个数值数组。 数组的内容将打印到控制台。
int[] array = new int[5];
在这里,我们声明一个包含五个元素的数组。 所有元素都是整数。
array[0] = 1;
array[1] = 2;
...
我们用一些数据初始化数组。 这是分配初始化。 索引在方括号中。 数字 1 将成为数组的第一个元素,数字 2 将成为第二个元素。
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
我们遍历数组并打印其元素。 数组具有Length
属性,该属性给出数组中元素的数量。 由于数组基于零,因此索引为0..length-1
。
我们可以在一个语句中声明并初始化一个数组。
Program.cs
using System;
namespace InitArray2
{
class Program
{
static void Main(string[] args)
{
int[] array = new int[] { 2, 4, 5, 6, 7, 3, 2 };
foreach (int i in array)
{
Console.WriteLine(i);
}
}
}
}
这是先前程序的修改版本。
int[] array = new int[] {2, 4, 5, 6, 7, 3, 2 };
一步就声明并初始化一个数组。 元素在大括号中指定。 我们没有指定数组的长度。 编译器将为我们完成此任务。
foreach (int i in array)
{
Console.WriteLine(i);
}
我们使用foreach
关键字遍历数组并打印其内容。
C# 数组访问元素
创建数组后,可以通过其索引访问其元素。 索引是放在数组名称后面方括号内的数字。
我们可以使用末尾^
运算符的索引从数组末尾获取元素。 ^0
等于array.Length
,^n
等于array.Length-n
。
Program.cs
using System;
namespace AccessElements
{
class Program
{
static void Main(string[] args)
{
string[] names = { "Jane", "Thomas", "Lucy", "David" };
Console.WriteLine(names[0]);
Console.WriteLine(names[1]);
Console.WriteLine(names[2]);
Console.WriteLine(names[3]);
Console.WriteLine("*************************");
Console.WriteLine(names[^1]);
Console.WriteLine(names[^2]);
Console.WriteLine(names[^3]);
Console.WriteLine(names[^4]);
}
}
}
在示例中,我们创建一个字符串名称数组。 我们通过其索引访问每个元素,并将它们打印到终端。
string[] names = { "Jane", "Thomas", "Lucy", "David" };
将创建一个字符串数组。
Console.WriteLine(names[0]);
Console.WriteLine(names[1]);
Console.WriteLine(names[2]);
Console.WriteLine(names[3]);
数组的每个元素都打印到控制台。 在names[0]
构造中,我们引用了名称数组的第一个元素。
Console.WriteLine(names[^1]);
Console.WriteLine(names[^2]);
Console.WriteLine(names[^3]);
Console.WriteLine(names[^4]);
我们从头开始访问数组元素。
$ dotnet run
Jane
Thomas
Lucy
David
*************************
David
Lucy
Thomas
Jane
运行示例,我们得到上面的输出。
C# 数组修改元素
可以修改数组的元素-它们不是不可变的。
Program.cs
using System;
namespace ModifyElements
{
class Program
{
static void Main(string[] args)
{
int[] vals = { 1, 2, 3, 4 };
vals[0] *= 2;
vals[1] *= 2;
vals[2] *= 2;
vals[3] *= 2;
Console.WriteLine("[{0}]", string.Join(", ", vals));
}
}
}
我们有一个由三个整数组成的数组。 每个值都将乘以 2。
int[] vals = { 1, 2, 3, 4 };
创建一个由三个整数组成的数组。
vals[0] *= 2;
vals[1] *= 2;
vals[2] *= 2;
vals[3] *= 2;
使用元素访问,我们将数组中的每个值乘以 2。
Console.WriteLine("[{0}]", string.Join(", ", vals));
使用Join()
方法,我们从数组的所有元素中创建一个字符串。 元素用逗号分隔。
$ dotnet run
[2, 4, 6, 8]
所有四个整数均已乘以数字 2。
C# 数组切片
我们可以使用..
运算符来获取数组切片。 范围指定范围的开始和结束。 范围的开始是包含范围的,但范围的结束是包含范围的。 这意味着起点包括在范围内,而终点不包括在范围内。
Program.cs
using System;
namespace ArrayRanges
{
class Program
{
static void Main(string[] args)
{
int[] vals = { 1, 2, 3, 4, 5, 6, 7 };
int[] vals2 = vals[1..5];
Console.WriteLine("[{0}]", string.Join(", ", vals2));
int[] vals3 = vals[..6];
Console.WriteLine("[{0}]", string.Join(", ", vals3));
int[] vals4 = vals[3..];
Console.WriteLine("[{0}]", string.Join(", ", vals4));
}
}
}
该示例适用于数组范围。
int[] vals2 = vals[1..5];
我们创建一个数组切片,其中包含从索引 1 到索引 4 的元素。
int[] vals3 = vals[..6];
如果省略起始索引,则切片将从索引 0 开始。
int[] vals4 = vals[3..];
如果省略了结束索引,则切片将一直持续到数组的末尾。
$ dotnet run
[2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
[4, 5, 6, 7]
这是输出。
C# 遍历数组
我们经常需要遍历数组的所有元素。 我们展示了两种遍历数组的常用方法。
Program.cs
using System;
namespace Traversing
{
class Program
{
static void Main(string[] args)
{
string[] planets = { "Mercury", "Venus", "Mars",
"Earth", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto" };
for (int i=0; i < planets.Length; i++)
{
Console.WriteLine(planets[i]);
}
foreach (string planet in planets)
{
Console.WriteLine(planet);
}
}
}
}
将创建一个行星名称数组。 我们使用for
和foreach
语句来打印所有值。
for (int i=0; i < planets.Length; i++)
{
Console.WriteLine(planets[i]);
}
在此循环中,我们利用了可以从数组对象中获取元素数量的事实。 元素数存储在Length
属性中。
foreach (string planet in planets)
{
Console.WriteLine(planet);
}
在遍历数组或其他集合时,可以使用foreach
语句使代码更紧凑。 在每个循环中,将行星变量传递给行星数组中的下一个值。
C# 数组大小
到目前为止,我们已经处理了一维数组。 指定元素所需的索引数称为数组的维或等级。
二维数组
接下来,我们将处理二维数组。
Program.cs
using System;
namespace TwoDimensions
{
class Program
{
static void Main(string[] args)
{
int[,] twodim = new int[,] { {1, 2, 3}, {1, 2, 3} };
int d1 = twodim.GetLength(0);
int d2 = twodim.GetLength(1);
for (int i=0; i<d1; i++)
{
for (int j=0; j<d2; j++)
{
Console.WriteLine(twodim[i, j]);
}
}
}
}
}
如果我们需要两个索引来访问数组中的元素,则我们有一个二维数组。
int[,] twodim = new int[,] { {1, 2, 3}, {1, 2, 3} };
我们在一个语句中声明并初始化一个二维数组。 请注意方括号内的逗号。
int d1 = twodim.GetLength(0);
int d2 = twodim.GetLength(1);
我们得到数组的大小。 GetLength()
获取数组指定维中的元素数。
for (int i=0; i<d1; i++)
{
for (int j=0; j<d2; j++)
{
Console.WriteLine(twodim[i, j]);
}
}
我们使用两个for
循环遍历二维数组的所有元素。 请注意,使用两个索引(以逗号分隔)可获得特定的数组元素。
$ dotnet run
1
2
3
1
2
3
这是代码示例的输出。
我们可以使用foreach
循环遍历二维数组。
Program.cs
using System;
namespace TraversingTwoDim
{
class Program
{
static void Main(string[] args)
{
int[,] vals = new int[4, 2] {
{ 9, 99 },
{ 3, 33 },
{ 4, 44 },
{ 1, 11 }
};
foreach (var val in vals)
{
Console.WriteLine(val);
}
}
}
}
通过foreach
循环,我们从头到尾一个一个地获取元素。
$ dotnet run
9
99
3
33
4
44
1
11
This is the output.
三维数组
接下来,我们将处理三维数组。
Program.cs
using System;
namespace ThreeDimensions
{
class Program
{
static void Main(string[] args)
{
int[,,] n3 = {
{{12, 2, 8}},
{{14, 5, 2}},
{{3, 26, 9}},
{{4, 11, 2}}
};
int d1 = n3.GetLength(0);
int d2 = n3.GetLength(1);
int d3 = n3.GetLength(2);
for (int i=0; i<d1; i++)
{
for (int j=0; j<d2; j++)
{
for (int k=0; k<d3; k++)
{
Console.Write(n3[i, j, k] + " ");
}
}
}
Console.Write('\n');
}
}
}
我们有一个数字三维数组。 同样,我们用数字初始化数组并将其打印到终端。
int[,,] n3 = {
{{12, 2, 8}},
{{14, 5, 2}},
{{3, 26, 9}},
{{4, 11, 2}}
};
左侧的方括号和右侧的其他花括号之间还有另一个逗号。
for (int k=0; k<d3; k++)
{
Console.Write(n3[i, j, k] + " ");
}
该循环经过三维。 我们使用三个索引从数组中检索值。
$ dotnet run
12 2 8 14 5 2 3 26 9 4 11 2
我们将三维数组的内容打印到控制台。
维数
有Rank
属性,它提供数组的维数。
Program.cs
using System;
namespace Rank
{
class Program
{
static void Main(string[] args)
{
int[] a1 = { 1, 2 };
int[,] a2 = { { 1 }, { 2 } };
int[,,] a3 = { { { 1, 2 }, { 2, 1 } } };
Console.WriteLine(a1.Rank);
Console.WriteLine(a2.Rank);
Console.WriteLine(a3.Rank);
}
}
}
我们有三个数组。 我们使用Rank
属性获取每个大小的数量。
Console.WriteLine(a1.Rank);
在这里,我们获得第一个数组的排名。
$ dotnet run
1
2
3
这是程序的输出。
C# 锯齿状数组
具有相同大小元素的数组称为矩形数组。 相反,具有不同大小元素的数组称为锯齿状数组。 锯齿状数组的声明和初始化方式不同。
Program.cs
using System;
namespace Jagged
{
class Program
{
static void Main(string[] args)
{
int[][] jagged = new int[][]
{
new int[] { 1, 2 },
new int[] { 1, 2, 3 },
new int[] { 1, 2, 3, 4 }
};
foreach (int[] array in jagged)
{
foreach (int e in array)
{
Console.Write(e + " ");
}
}
Console.Write('\n');
}
}
}
这是一个锯齿状数组的示例。
int[][] jagged = new int[][]
{
new int[] { 1, 2 },
new int[] { 1, 2, 3 },
new int[] { 1, 2, 3, 4 }
};
这是锯齿状数组的声明和初始化。 请注意,这一次,我们使用两对方括号。 我们有一个数组数组。 更具体地说,我们已经声明了一个数组,其中包含三个int
数据类型的数组。 每个数组具有不同数量的元素。
foreach (int[] array in jagged)
{
foreach (int e in array)
{
Console.Write(e + " ");
}
}
我们使用两个foreach
循环遍历锯齿状数组。 在第一个循环中,我们得到数组。 在第二个循环中,我们获取获得的数组的元素。
C# 数组方法
有多种使用数组的方法。 这些方法可用于检索,修改,排序,复制,搜索数据。 我们使用的这些方法是Array
类的静态方法或数组对象的成员方法。
Program.cs
using System;
namespace Sorting
{
class Program
{
static void Main(string[] args)
{
string[] names = {"Jane", "Frank", "Alice", "Tom" };
Array.Sort(names);
foreach(string el in names)
{
Console.Write(el + " ");
}
Console.Write('\n');
Array.Reverse(names);
foreach(string el in names)
{
Console.Write(el + " ");
}
Console.Write('\n');
}
}
}
在此示例中,我们对数据进行排序。
string[] names = {"Jane", "Frank", "Alice", "Tom" };
我们有一个字符串数组。
Array.Sort(names);
静态Sort()
方法按字母顺序对数据进行排序。
Array.Reverse(names);
Reverse()
方法反转整个一维数组中元素的顺序。
$ dotnet run
Alice Frank Jane Tom
Tom Jane Frank Alice
我们已经按升序和降序对名称进行了排序。
以下示例使用SeValue()
,GetValue()
,IndexOf()
,Copy()
和Clear()
方法。
Program.cs
using System;
namespace ArrayMethods
{
class Program
{
static void Main(string[] args)
{
string[] names = {"Jane", "Frank", "Alice", "Tom"};
string[] girls = new string[5];
names.SetValue("Beky", 1);
names.SetValue("Erzebeth", 3);
Console.WriteLine(names.GetValue(1));
Console.WriteLine(names.GetValue(3));
Console.WriteLine(Array.IndexOf(names, "Erzebeth"));
Array.Copy(names, girls, names.Length);
foreach(string girl in girls)
{
Console.Write(girl + " ");
}
Console.Write('\n');
Array.Clear(names, 0, 2);
foreach(string name in names)
{
Console.Write(name + " ");
}
Console.Write('\n');
}
}
}
本示例介绍了其他方法。
names.SetValue("Beky", 1);
names.SetValue("Erzebeth", 3);
SetValue()
为数组中的特定索引设置一个值。
Console.WriteLine(names.GetValue(1));
Console.WriteLine(names.GetValue(3));
我们使用GetValue()
方法从数组中检索值。
Console.WriteLine(Array.IndexOf(names, "Erzebeth"));
IndexOf()
方法返回首次出现特定值的索引。
Array.Copy(names, girls, names.Length);
Copy()
方法将值从源数组复制到目标数组。 第一个参数是源数组,第二个参数是目标数组。 第三个参数是长度; 它指定要复制的元素数。
Array.Clear(names, 0, 2);
Clear()
方法从数组中删除所有元素。 它包含三个参数:数组,起始索引和要从索引中清除的元素数。
$ dotnet run
Beky
Erzebeth
3
Jane Beky Alice Erzebeth
Alice Erzebeth
This is the output.
在 C# 教程的这一部分中,我们使用了数组。
{% endraw %}
C# 面向对象编程
在 C# 教程的这一部分中,我们将讨论 C# 中的面向对象编程。
共有三种广泛使用的编程范例:过程编程,函数编程和面向对象的编程。 C# 支持过程式编程和面向对象的编程。
OOP 定义
面向对象编程(OOP)是一种使用对象及其相互作用设计应用和计算机程序的编程范例。
OOP 中有一些基本的编程概念:
- 抽象
- 多态
- 封装
- 继承
抽象通过建模适合该问题的类来简化复杂的现实。 多态是将运算符或函数以不同方式用于不同数据输入的过程。 封装对其他对象隐藏了类的实现细节。 继承是一种使用已经定义的类形成新类的方法。
C# 对象
对象是 C# OOP 程序的基本构建块。 对象是数据和方法的组合。 数据和方法称为对象的成员。 在 OOP 程序中,我们创建对象。 这些对象通过方法进行通信。 每个对象都可以接收消息,发送消息和处理数据。
创建对象有两个步骤。 首先,我们定义一个类。 类是对象的模板。 它是一个蓝图,描述了类对象共享的状态和行为。 一个类可以用来创建许多对象。 在运行时从类创建的对象称为该特定类的实例。
Program.cs
using System;
namespace Being
{
class Being {}
class Program
{
static void Main(string[] args)
{
var b = new Being();
Console.WriteLine(b);
}
}
}
在第一个示例中,我们创建一个简单的对象。
class Being {}
这是一个简单的类定义。 模板的主体为空。 它没有任何数据或方法。
var b = new Being();
我们创建Being
类的新实例。 为此,我们使用了new
关键字。 b
变量是创建对象的句柄。
Console.WriteLine(b);
我们将对象打印到控制台以获取该对象的一些基本描述。 打印对象是什么意思? 实际上,当我们打印对象时,我们将其称为ToString()
方法。 但是我们还没有定义任何方法。 这是因为创建的每个对象都继承自基本object
。 它具有一些基本功能,可以在所有创建的对象之间共享。 其中之一是ToString()
方法。
$ dotnet run
Being.Being
我们得到对象类名。
C# 对象属性
对象属性是捆绑在类实例中的数据。 对象属性称为实例变量或成员字段。 实例变量是在类中定义的变量,该类中的每个对象都有一个单独的副本。
Program.cs
using System;
namespace ObjectAttributes
{
class Person
{
public string name;
}
class Program
{
static void Main(string[] args)
{
var p1 = new Person();
p1.name = "Jane";
var p2 = new Person();
p2.name = "Beky";
Console.WriteLine(p1.name);
Console.WriteLine(p2.name);
}
}
}
在上面的 C# 代码中,我们有一个带有一个成员字段的Person
类。
class Person
{
public string name;
}
我们声明一个名称成员字段。 public
关键字指定可以在类块之外访问成员字段。
var p1 = new Person();
p1.name = "Jane";
我们创建Person
类的实例,并将名称变量设置为"Jane"
。 我们使用点运算符来访问对象的属性。
var p2 = new Person();
p2.name = "Beky";
我们创建Person
类的另一个实例。 在这里,我们将变量设置为"Beky"
。
Console.WriteLine(p1.name);
Console.WriteLine(p2.name);
我们将变量的内容打印到控制台。
$ dotnet run
Jane
Beky
我们看到了程序的输出。 Person
类的每个实例都有一个单独的名称成员字段副本。
C# 方法
方法是在类主体内定义的函数。 它们用于通过对象的属性执行操作。 方法将模块化带入我们的程序。
在 OOP 范式的封装概念中,方法至关重要。 例如,我们的AccessDatabase
类中可能有一个Connect()
方法。 我们无需知道方法Connect()
如何精确地连接到数据库。 我们只需要知道它用于连接数据库。 这对于划分编程中的职责至关重要,尤其是在大型应用中。
对象将状态和行为分组,方法代表对象的行为部分。
Program.cs
using System;
namespace Methods
{
class Circle
{
private int radius;
public void SetRadius(int radius)
{
this.radius = radius;
}
public double Area()
{
return this.radius * this.radius * Math.PI;
}
}
class Program
{
static void Main(string[] args)
{
var c = new Circle();
c.SetRadius(5);
Console.WriteLine(c.Area());
}
}
}
在代码示例中,我们有一个Circle
类。 我们定义了两种方法。
private int radius;
我们只有一个成员字段。 它是圆的半径。 private
关键字是访问说明符。 它表明变量仅限于外部世界。 如果要从外部修改此变量,则必须使用公共可用的SetRadius()
方法。 这样我们可以保护我们的数据。
public void SetRadius(int radius)
{
this.radius = radius;
}
这是SetRadius()
方法。 this
变量是一个特殊变量,我们用它来访问方法中的成员字段。 this.radius
是实例变量,而半径是局部变量,仅在SetRadius()
方法内部有效。
var c = new Circle();
c.SetRadius(5);
我们创建Circle
类的实例,并通过在圆对象上调用SetRadius()
方法来设置其半径。 我们使用点运算符来调用该方法。
public double Area()
{
return this.radius * this.radius * Math.PI;
}
Area()
方法返回圆的面积。 Math.PI
是内置常数。
$ dotnet run
78.5398163397448
运行该示例可得出此结果。
C# 访问修饰符
访问修饰符设置方法和成员字段的可见性。 C# 具有四个基本访问修饰符:public
,protected
,private
和internal
。 可以从任何地方访问public
成员。 protected
成员只能在类本身内部以及继承的和父类访问。 private
成员仅限于包含类型,例如仅在其类或接口内。 可以从同一程序集(exe 或 DLL)中访问internal
成员。
修饰符还有两种组合:protected internal
和private protected
。 protected internal
类型或成员可以由声明它的程序集中的任何代码访问,也可以从另一个程序集中的派生类中访问。 private protected
类型或成员只能在其声明程序集中通过同一个类或从该类派生的类型的代码进行访问。
访问修饰符可防止意外修改数据。 它们使程序更强大。
类 | 当前程序集 | 派生类 | 当前程序集中的派生类 | 整个程序 | |
---|---|---|---|---|---|
public |
+ |
+ |
+ |
+ |
+ |
protected |
+ |
o |
+ |
+ |
o |
internal |
+ |
+ |
o |
o |
o |
private |
+ |
o |
o |
o |
o |
protected internal |
+ |
+ |
+ |
+ |
o |
private protected |
+ |
o |
o |
+ |
o |
上表总结了 C# 访问修饰符(+
是可访问的,o
是不可访问的)。
Program.cs
using System;
namespace AccessModifiers
{
class Person
{
public string name;
private int age;
public int GetAge()
{
return this.age;
}
public void SetAge(int age)
{
this.age = age;
}
}
class Program
{
static void Main(string[] args)
{
var p = new Person();
p.name = "Jane";
p.SetAge(17);
Console.WriteLine("{0} is {1} years old",
p.name, p.GetAge());
}
}
}
在上面的程序中,我们有两个成员字段。 一个被宣布为公开,另一个被宣布为私有。
public int GetAge()
{
return this.age;
}
如果成员字段是private
,则访问它的唯一方法是通过方法。 如果要在类外部修改属性,则必须将方法声明为public
。 这是数据保护的重要方面。
public void SetAge(int age)
{
this.age = age;
}
SetAge()
方法使我们能够从类定义之外更改private
年龄变量。
var p = new Person();
p.name = "Jane";
我们创建Person
类的新实例。 因为名称属性是public
,所以我们可以直接访问它。 但是,不建议这样做。
p.SetAge(17);
SetAge()
方法修改年龄成员字段。 由于已声明private
,因此无法直接访问或修改。
Console.WriteLine("{0} is {1} years old",
p.name, p.GetAge());
最后,我们访问两个成员以构建一个字符串。
$ dotnet run
Jane is 17 years old
运行示例将给出此输出。
具有private
访问修饰符的成员字段不被派生类继承。
Program.cs
using System;
namespace Protected
{
class Base
{
public string name = "Base";
protected int id = 5323;
private bool isDefined = true;
}
class Derived : Base
{
public void info()
{
Console.WriteLine("This is Derived class");
Console.WriteLine("Members inherited");
Console.WriteLine(this.name);
Console.WriteLine(this.id);
// Console.WriteLine(this.isDefined);
}
}
class Program
{
static void Main(string[] args)
{
var derived = new Derived();
derived.info();
}
}
}
在前面的程序中,我们有一个Derived
类,该类继承自Base
类。 Base
类具有三个成员字段。 全部具有不同的访问修饰符。 isDefined
成员不继承。 private
修饰符可以防止这种情况。
class Derived : Base
类Derived
继承自Base
类。 要从另一个类继承,我们使用冒号(:)运算符。
Console.WriteLine(this.name);
Console.WriteLine(this.id);
// Console.WriteLine(this.isDefined);
public
和protected
成员由Derived
类继承。 可以访问它们。 private
成员未继承。 访问成员字段的行被注释。 如果我们取消注释该行,则代码将无法编译。
$ dotnet run
Program.cs(9,22): warning CS0414: The field 'Base.isDefined' is assigned but its value
is never used [C:\Users\Jano\Documents\csharp\tutorial\oop\Protected\Protected.csproj]
This is Derived class
Members inherited
Base
5323
运行程序,我们收到此输出。
C# 构造器
构造器是一种特殊的方法。 创建对象时会自动调用它。 构造器不返回值。 构造器的目的是初始化对象的状态。 构造器与类具有相同的名称。 构造器是方法,因此它们也可以重载。
构造器不能被继承。 它们按继承顺序被调用。 如果我们不为类编写任何构造器,则 C# 提供一个隐式默认构造器。 如果提供任何类型的构造器,则不提供默认值。
Program.cs
using System;
namespace Constructor
{
class Being
{
public Being()
{
Console.WriteLine("Being is created");
}
public Being(string being)
{
Console.WriteLine("Being {0} is created", being);
}
}
class Program
{
static void Main(string[] args)
{
new Being();
new Being("Tom");
}
}
}
我们有一个Being
类。 此类具有两个构造器。 第一个不带参数; 第二个采用一个参数。
public Being(string being)
{
Console.WriteLine("Being {0} is created", being);
}
此构造器采用一个字符串参数。
new Being();
创建Being
类的实例。 这次,在创建对象时调用没有参数的构造器。
$ dotnet run
Being is created
Being Tom is created
这是程序的输出。
在下一个示例中,我们初始化类的数据成员。 变量的初始化是构造器的典型工作。
Program.cs
using System;
namespace Constructor2
{
class MyFriend
{
private DateTime born;
private string name;
public MyFriend(string name, DateTime born)
{
this.name = name;
this.born = born;
}
public void Info()
{
Console.WriteLine("{0} was born on {1}",
this.name, this.born.ToShortDateString());
}
}
class Program
{
static void Main(string[] args)
{
var name = "Lenka";
var born = new DateTime(1990, 3, 5);
var friend = new MyFriend(name, born);
friend.Info();
}
}
}
我们有一个带有数据成员和方法的MyFriend
类。
private DateTime born;
private string name;
类定义中有两个私有变量。
public MyFriend(string name, DateTime born)
{
this.name = name;
this.born = born;
}
在构造器中,我们启动两个数据成员。 this
变量是用于引用对象变量的处理器。
var friend = new MyFriend(name, born);
friend.Info();
我们创建带有两个参数的MyFriend
对象。 然后我们调用对象的Info()
方法。
$ dotnet run
Lenka was born on 3/5/1990
这是输出。
C# 构造器链接
构造器链接是类从构造器调用另一个构造器的能力。 要从同一类调用另一个构造器,我们使用this
关键字。
Program.cs
using System;
namespace ConstructorChaining
{
class Circle
{
public Circle(int radius)
{
Console.WriteLine("Circle, r={0} is created", radius);
}
public Circle() : this(1)
{
}
}
class Program
{
static void Main(string[] args)
{
new Circle(5);
new Circle();
}
}
}
我们有一个Circle
类。 该类具有两个构造器。 一种采用一个参数,一种不采用任何参数。
public Circle(int radius)
{
Console.WriteLine("Circle, r={0} is created", radius);
}
此构造器采用一个参数-radius
。
public Circle() : this(1)
{
}
这是没有参数的构造器。 它只是简单地调用另一个构造器,并为其提供默认半径 1。
$ dotnet run
Circle, r=5 is created
Circle, r=1 is created
This is the output.
C# ToString
方法
每个对象都有一个ToString()
方法。 它返回人类可读的对象表示形式。 默认实现返回Object
类型的标准名称。 请注意,当我们使用对象作为参数调用Console.WriteLine()
方法时,将调用ToString()
。
Program.cs
using System;
namespace ToStringMethod
{
class Being
{
public override string ToString()
{
return "This is Being class";
}
}
class Program
{
static void Main(string[] args)
{
var b = new Being();
var o = new Object();
Console.WriteLine(o.ToString());
Console.WriteLine(b.ToString());
Console.WriteLine(b);
}
}
}
我们有一个Being
类,其中我们重写了ToString()
方法的默认实现。
public override string ToString()
{
return "This is Being class";
}
创建的每个类都从基object
继承。 ToString()
方法属于此对象类。 我们使用override
关键字来通知我们正在覆盖方法。
var b = new Being();
var o = new Object();
我们创建一个自定义对象和一个内置对象。
Console.WriteLine(o.ToString());
Console.WriteLine(b.ToString());
我们在这两个对象上调用ToString()
方法。
Console.WriteLine(b);
正如我们之前指定的,将对象作为Console.WriteLine()
的参数将调用其ToString()
方法。 这次,我们隐式调用了该方法。
$ dotnet run
System.Object
This is Being class
This is Being class
这是我们运行示例时得到的。
C# 对象初始化器
对象初始化器让我们在创建时将值分配给对象的任何可访问字段或属性,而无需调用构造器。 属性或字段在{}
括号内分配。 另外,我们可以为构造器指定参数,也可以省略参数。
Program.cs
using System;
namespace ObjectInitializers
{
class User
{
public User() {}
public string Name { set; get; }
public string Occupation { set; get; }
public override string ToString()
{
return $"{Name} is a {Occupation}";
}
}
class Program
{
static void Main(string[] args)
{
var u = new User { Name = "John Doe", Occupation = "gardener" };
Console.WriteLine(u);
}
}
}
在示例中,我们使用对象初始化器语法创建一个新用户。
public User() {}
我们定义一个空的构造器。
public string Name { set; get; }
public string Occupation { set; get; }
我们有两个属性:Name
和Occupation
。
var u = new User { Name = "John Doe", Occupation = "gardener" };
我们将值分配给{}
括号中的属性。
$ dotnet run
John Doe is a gardener
This is the output.
C# 类常量
C# 可以创建类常量。 这些常量不属于具体对象。 他们属于类。 按照约定,常量用大写字母表示。
Program.cs
using System;
namespace ClassConstants
{
class Math
{
public const double PI = 3.14159265359;
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Math.PI);
}
}
}
我们有一个带有PI
常量的Math
类。
public const double PI = 3.14159265359;
const
关键字用于定义常数。 public
关键字使它可以在类的主体之外访问。
$ dotnet run
3.14159265359
运行示例,我们看到此输出。
C# 继承
继承是使用已经定义的类形成新类的方法。 新形成的类称为派生的类,我们派生的类称为基类。 继承的重要好处是代码重用和降低程序的复杂性。 派生类(后代)将覆盖或扩展基类(祖先)的功能。
Program.cs
using System;
namespace Inheritance
{
class Being
{
public Being()
{
Console.WriteLine("Being is created");
}
}
class Human : Being
{
public Human()
{
Console.WriteLine("Human is created");
}
}
class Program
{
static void Main(string[] args)
{
new Human();
}
}
}
在此程序中,我们有两个类。 基类Being
和派生的Human
类。 派生类继承自基类。
class Human : Being
在 C# 中,我们使用冒号(:
)运算符创建继承关系。
new Human();
我们实例化派生的Human
类。
$ dotnet run
Being is created
Human is created
我们可以看到两个构造器都被调用了。 首先,调用基类的构造器,然后调用派生类的构造器。
接下来是一个更复杂的示例。
Program.cs
using System;
namespace Inheritance2
{
class Being
{
static int count = 0;
public Being()
{
count++;
Console.WriteLine("Being is created");
}
public void GetCount()
{
Console.WriteLine("There are {0} Beings", count);
}
}
class Human : Being
{
public Human()
{
Console.WriteLine("Human is created");
}
}
class Animal : Being
{
public Animal()
{
Console.WriteLine("Animal is created");
}
}
class Dog : Animal
{
public Dog()
{
Console.WriteLine("Dog is created");
}
}
class Program
{
static void Main(string[] args)
{
new Human();
var dog = new Dog();
dog.GetCount();
}
}
}
我们有四个类。 继承层次更加复杂。 Human
和Animal
类继承自Being
类。 Dog 类直接继承自Animal
类,并间接继承自Being
类。 我们还介绍了static
变量的概念。
static int count = 0;
我们定义一个static
变量。 静态成员是类的所有实例共享的成员。
Being()
{
count++;
Console.WriteLine("Being is created");
}
每次实例化Being
类时,我们将count
变量增加一。 这样,我们就可以跟踪创建的实例数。
class Animal : Being
...
class Dog : Animal
...
Animal
继承自Being
,Dog
继承自Animal
。 Dog
也间接继承自Being
。
new Human();
var dog = new Dog();
dog.GetCount();
我们从Human
和Dog
类创建实例。 我们称为Dog
对象的GetCount()
方法。
$ dotnet run
Being is created
Human is created
Being is created
Animal is created
Dog is created
There are 2 Beings
Human
调用两个构造器。 Dog
调用三个构造器。 有两个实例化的存在。
我们使用base
关键字显式调用父级的构造器。
Program.cs
using System;
namespace Shapes
{
class Shape
{
protected int x;
protected int y;
public Shape()
{
Console.WriteLine("Shape is created");
}
public Shape(int x, int y)
{
this.x = x;
this.y = y;
}
}
class Circle : Shape
{
private int r;
public Circle(int r, int x, int y) : base(x, y)
{
this.r = r;
}
public override string ToString()
{
return String.Format("Circle, r:{0}, x:{1}, y:{2}", r, x, y);
}
}
class Program
{
static void Main(string[] args)
{
var c = new Circle(2, 5, 6);
Console.WriteLine(c);
}
}
}
我们有两个类:Shape
类和Circle
类。 Shape
类是几何形状的基类。 我们可以在此类中加入一些常见形状的共同点,例如x
和y
坐标。
public Shape()
{
Console.WriteLine("Shape is created");
}
public Shape(int x, int y)
{
this.x = x;
this.y = y;
}
Shape
类具有两个构造器。 第一个是默认构造器。 第二个参数有两个参数:x
,y
坐标。
public Circle(int r, int x, int y) : base(x, y)
{
this.r = r;
}
这是Circle
类的构造器。 此构造器启动r
成员并调用父级的第二个构造器,并向其传递x
和y
坐标。 如果不使用base
关键字显式调用构造器,则将调用Shape
类的默认构造器。
$ dotnet run
Circle, r:2, x:5, y:6
这是示例的输出。
C# 抽象类和方法
抽象类无法实例化。 如果一个类至少包含一个抽象方法,则也必须将其声明为抽象方法。 抽象方法无法实现; 他们只是声明方法的签名。 当我们从抽象类继承时,所有抽象方法都必须由派生类实现。 此外,必须以较少受限制的可见性声明这些方法。
与接口不同,抽象类可能具有完全实现的方法,并且可能具有定义的成员字段。 因此,抽象类可以提供部分实现。 程序员经常将一些通用功能放入抽象类中。 这些抽象类随后会被子类化以提供更具体的实现。 例如,Qt 图形库具有QAbstractButton
,它是按钮小部件的抽象基类,提供按钮所共有的功能。 按钮Q3Button
,QCheckBox
,QPushButton
,QRadioButton
和QToolButton
都从此基本抽象类继承。
正式地说,抽象类用于强制执行协议。 协议是所有实现对象都必须支持的一组操作。
Program.cs
using System;
namespace AbstractClass
{
abstract class Drawing
{
protected int x = 0;
protected int y = 0;
public abstract double Area();
public string GetCoordinates()
{
return string.Format("x: {0}, y: {1}", this.x, this.y);
}
}
class Circle : Drawing
{
private int r;
public Circle(int x, int y, int r)
{
this.x = x;
this.y = y;
this.r = r;
}
public override double Area()
{
return this.r * this.r * Math.PI;
}
public override string ToString()
{
return string.Format("Circle at x: {0}, y: {1}, radius: {2}",
this.x, this.y, this.r);
}
}
class Program
{
static void Main(string[] args)
{
var c = new Circle(12, 45, 22);
Console.WriteLine(c);
Console.WriteLine("Area of circle: {0}", c.Area());
Console.WriteLine(c.GetCoordinates());
}
}
}
我们有一个抽象基类Drawing
。 该类定义两个成员字段,定义一个方法并声明一个方法。 一种方法是抽象的,另一种是完全实现的。 Drawing
类是抽象的,因为我们无法绘制它。 我们可以画一个圆,一个点或一个正方形。 Drawing
类对我们可以绘制的对象具有一些通用功能。
abstract class Drawing
我们使用abstract
关键字定义一个抽象类。
public abstract double Area();
抽象方法之前还带有abstract
关键字。
class Circle : Drawing
圆是Drawing
类的子类。 它必须实现抽象的Area()
方法。
public override double Area()
{
return this.r * this.r * Math.PI;
}
当我们实现Area()
方法时,必须使用override
关键字。 这样,我们通知编译器我们将覆盖现有的(继承的)方法。
$ dotnet run
Circle at x: 12, y: 45, radius: 22
Area of circle: 1520.53084433746
x: 12, y: 45
This is the output of the program.
C# 部分类
使用partial
关键字,可以将类的定义拆分到同一名称空间中的几个部分中。 该类也可以在多个文件中定义。
当使用非常大的代码库时可以使用部分类,这些代码库可以拆分为较小的单元。 局部类也与自动代码生成器一起使用。
Program.cs
using System;
namespace PartialClass
{
partial class Worker
{
public string DoWork()
{
return "Doing work";
}
}
partial class Worker
{
public string DoPause()
{
return "Pausing";
}
}
class Program
{
static void Main(string[] args)
{
var worker = new Worker();
Console.WriteLine(worker.DoWork());
Console.WriteLine(worker.DoWork());
Console.WriteLine(worker.DoPause());
}
}
}
在示例中,我们将Worker
类定义为两部分。 这些部分由编译器连接在一起以形成最终类。
$ dotnet run
Doing work
Doing work
Pausing
这是输出。
这是 C# 中 OOP 描述的第一部分。
C# 中的方法
在本教程的这一部分中,我们将介绍 C# 方法。
在面向对象的编程中,我们使用对象。 对象是程序的基本构建块。 对象由数据和方法组成。 方法更改创建的对象的状态。 它们是对象的动态部分。 数据是静态部分。
C# 方法定义
方法是包含一系列语句的代码块。 方法必须在类或结构中声明。 好的编程习惯是方法仅执行一项特定任务。 方法为程序带来了模块化。 正确使用方法具有以下优点:
- 减少代码重复
- 将复杂的问题分解成更简单的部分
- 提高代码的清晰度
- 重用代码
- 信息隐藏
C# 方法特征
方法的基本特征是:
- 访问权限
- 返回值类型
- 方法名称
- 方法参数
- 括号
- 语句块
方法的访问级别由访问修饰符控制。 他们设置方法的可见性。 他们确定谁可以调用该方法。 方法可以将值返回给调用方。 如果我们的方法返回一个值,我们将提供其数据类型。 如果不是,则使用void
关键字指示我们的方法不返回值。 方法参数用括号括起来,并用逗号分隔。 空括号表示该方法不需要任何参数。 方法块周围包含{}个字符。 当方法被调用时,该块包含一个或多个执行的语句。 拥有一个空的方法块是合法的。
C# 方法签名
方法签名是 C# 编译器方法的唯一标识。 签名由方法名称以及其每个形式参数的类型和种类(值,引用或输出)组成。 方法签名不包括返回类型。
可以在方法名称中使用任何合法字符。 按照约定,方法名称以大写字母开头。 方法名称是动词或动词,后跟形容词或名词。 随后的每个单词都以大写字母开头。 以下是 C# 中方法的典型名称:
Execute
FindId
SetName
GetName
CheckIfValid
TestValidity
C# 简单示例
我们从一个简单的例子开始。
Program.cs
using System;
namespace SimpleMethod
{
class Base
{
public void ShowInfo()
{
Console.WriteLine("This is Base class");
}
}
class Program
{
static void Main(string[] args)
{
Base bs = new Base();
bs.ShowInfo();
}
}
}
我们有一个ShowInfo()
方法,它打印其类的名称。
class Base
{
public void ShowInfo()
{
Console.WriteLine("This is Base class");
}
}
每个方法都必须在类或结构内定义。 它必须有一个名字。 在我们的情况下,名称为ShowInfo()
。 方法名称之前的关键字是访问说明符和返回类型。 括号跟随方法的名称。 它们可能包含方法的参数。 我们的方法没有任何参数。
static void Main()
{
...
}
这是Main()
方法。 它是每个控制台或 GUI 应用的入口点。 必须声明为static
。 我们将在后面看到原因。 Main()
方法的返回类型可以是void
或int
。 省略了Main()
方法的访问说明符。 在这种情况下,将使用默认值private
。 不建议对Main()
方法使用public
访问说明符。 程序集中的任何其他方法都不应调用它。 当应用启动时,只有 CLR 才能调用它。
Base bs = new Base();
bs.ShowInfo();
我们创建Base
类的实例。 我们在对象上调用ShowInfo()
方法。 我们说该方法是一个实例方法,因为它需要一个实例来调用。 通过指定对象实例,成员访问运算符(点),方法名称,来调用该方法。
C# 方法参数
参数是传递给方法的值。 方法可以采用一个或多个参数。 如果方法使用数据,则必须将数据传递给方法。 我们通过在括号内指定它们来实现。 在方法定义中,我们必须为每个参数提供名称和类型。
Program.cs
using System;
namespace MethodParameters
{
class Addition
{
public int AddTwoValues(int x, int y)
{
return x + y;
}
public int AddThreeValues(int x, int y, int z)
{
return x + y + z;
}
}
class Program
{
static void Main(string[] args)
{
Addition a = new Addition();
int x = a.AddTwoValues(12, 13);
int y = a.AddThreeValues(12, 13, 14);
Console.WriteLine(x);
Console.WriteLine(y);
}
}
}
在上面的示例中,我们有两种方法。 其中一个带有两个参数,另一个带有三个参数。
public int AddTwoValues(int x, int y)
{
return x + y;
}
AddTwoValues()
方法采用两个参数。 这些参数具有int
类型。 该方法还向调用者返回一个整数。 我们使用return
关键字从方法中返回一个值。
public int AddThreeValues(int x, int y, int z)
{
return x + y + z;
}
AddThreeValues()
与先前的方法相似。 它带有三个参数。
int x = a.AddTwoValues(12, 13);
我们称为加法对象的AddTwoValues()
方法。 它有两个值。 这些值将传递给方法。 该方法返回一个分配给x
变量的值。
C# 变量数量可变
方法可以采用可变数量的参数。 为此,我们使用params
关键字。 params
关键字之后不允许有其他参数。 方法声明中仅允许使用一个params
关键字。
Program.cs
using System;
namespace SumOfValues
{
class Program
{
static void Main(string[] args)
{
Sum(1, 2, 3);
Sum(1, 2, 3, 4, 5);
}
static void Sum(params int[] list)
{
Console.WriteLine("There are {0} items", list.Length);
int sum = 0;
foreach (int i in list)
{
sum = sum + i;
}
Console.WriteLine("Their sum is {0}", sum);
}
}
}
我们创建一个Sum()
方法,该方法可以使用可变数量的参数。 该方法将计算传递给该方法的值的总和。
Sum(1, 2, 3);
Sum(1, 2, 3, 4, 5);
我们两次调用Sum()
方法。 在一种情况下,它需要 3 个参数,在第二种情况下,它需要 5 个参数。我们调用相同的方法。
static void Sum(params int[] list)
{
...
}
Sum()
方法可以采用可变数量的整数值。 所有值都添加到列表数组中。
Console.WriteLine("There are {0} items", list.Length);
我们打印列表数组的长度。
int sum = 0;
foreach (int i in list)
{
sum = sum + i;
}
我们计算列表中值的总和。
$ dotnet run
There are 3 items
Their sum is 6
There are 5 items
Their sum is 15
这是示例的输出。
C# 返回元组
C# 方法可以使用元组返回多个值。
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace ReturingTuples
{
class Program
{
static void Main(string[] args)
{
var vals = new List<int> { 11, 21, 3, -4, -15, 16, 5 };
(int min, int max, int sum) = BasicStats(vals);
Console.WriteLine($"Minimum: {min}, Maximum: {max}, Sum: {sum}");
}
static (int, int, int) BasicStats(List<int> vals)
{
int sum = vals.Sum();
int min = vals.Min();
int max = vals.Max();
return (min, max, sum);
}
}
}
我们有BasicStats()
方法,该方法返回整数列表的基本统计信息。
using System.Linq;
我们需要为Sum()
,Min()
和Max()
扩展方法导入System.Linq
。
var vals = new List<int> { 11, 21, 3, -4, -15, 16, 5 };
我们有一个整数值列表。 我们要根据这些值计算一些基本统计数据。
(int min, int max, int sum) = BasicStats(vals);
我们使用解构操作将元组元素分配给三个变量。
static (int, int, int) BasicStats(List<int> vals)
{
方法声明指定我们返回一个元组。
return (min, max, sum);
我们返回三个元素的元组。
$ dotnet run
Minimum: -15, Maximum: 21, Sum: 37
这是输出。
C# 匿名方法
匿名方法是没有名称的内联方法。 匿名方法消除了创建单独方法的需要,从而减少了编码开销。 如果没有匿名方法,开发者通常不得不创建一个类来仅调用一个方法。
Program.cs
using System;
using System.Timers;
namespace AnonymousMethod
{
class Program
{
static void Main(string[] args)
{
var timer = new Timer();
timer.Elapsed += new ElapsedEventHandler(
delegate(object source, ElapsedEventArgs e)
{
Console.WriteLine("Event triggered at {0}", e.SignalTime);
}
);
timer.Interval = 2000;
timer.Enabled = true;
Console.ReadLine();
}
}
}
我们创建一个计时器对象,然后每 2 秒调用一个匿名方法。
var timer = new Timer();
Timer
类在应用中生成重复事件。
timer.Elapsed += new ElapsedEventHandler(
delegate(object source, ElapsedEventArgs e)
{
Console.WriteLine("Event triggered at {0}", e.SignalTime);
}
);
在这里,我们将匿名方法插入Elapsed
事件。 delegate
关键字用于表示匿名方法。
Console.ReadLine();
此时,程序等待来自用户的输入。 当我们按下Return
键时,程序结束。 否则,程序将在事件生成之前立即完成。
C# 通过值,通过引用传递参数
C# 支持两种将参数传递给方法的方式:按值和按引用。 参数的默认传递是按值传递。 当我们按值传递参数时,该方法仅适用于值的副本。 当我们处理大量数据时,这可能会导致性能开销。
我们使用ref
关键字通过引用传递值。 当我们通过引用传递值时,该方法会收到对实际值的引用。 修改后,原始值会受到影响。 这种传递值的方式更加节省时间和空间。 另一方面,它更容易出错。
我们应该使用哪种方式传递参数? 这取决于实际情况。 假设我们有一组数据,例如员工工资。 如果我们要计算数据的某些统计信息,则无需修改它们。 我们可以传递值。 如果我们处理大量数据,并且计算速度至关重要,则可以引用。 如果我们要修改数据,例如进行一些减薪或加薪,我们可以引用一下。
以下示例显示了如何通过值传递参数。
Program.cs
using System;
namespace PassingByValues
{
class Program
{
static int a = 4;
static int b = 7;
static void Main(string[] args)
{
Console.WriteLine("Outside Swap method");
Console.WriteLine("a is {0}", a);
Console.WriteLine("b is {0}", b);
Swap(a, b);
Console.WriteLine("Outside Swap method");
Console.WriteLine("a is {0}", a);
Console.WriteLine("b is {0}", b);
}
static void Swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
Console.WriteLine("Inside Swap method");
Console.WriteLine("a is {0}", a);
Console.WriteLine("b is {0}", b);
}
}
}
Swap()
方法在a
和b
变量之间交换数字。 原始变量不受影响。
static int a = 4;
static int b = 7;
最初,这两个变量被启动。 变量必须声明为static
,因为它们是从静态方法中使用的。
Swap(a, b);
我们称为Swap()
方法。 该方法将a
和b
变量作为参数。
int temp = a;
a = b;
b = temp;
在Swap()
方法内部,我们更改了值。 请注意,a
和b
变量是在本地定义的。 它们仅在Swap()
方法内部有效。
$ dotnet run
Outside Swap method
a is 4
b is 7
Inside Swap method
a is 7
b is 4
Outside Swap method
a is 4
b is 7
输出显示原始变量不受影响。
下一个代码示例通过引用将值传递给方法。 原始变量在Swap()
方法内更改。 方法定义和方法调用都必须使用ref
关键字。
Program.cs
using System;
namespace PassingByReference
{
class Program
{
static int a = 4;
static int b = 7;
static void Main(string[] args)
{
Console.WriteLine("Outside Swap method");
Console.WriteLine("a is {0}", a);
Console.WriteLine("b is {0}", b);
Swap(ref a, ref b);
Console.WriteLine("Outside Swap method");
Console.WriteLine("a is {0}", a);
Console.WriteLine("b is {0}", b);
}
static void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
Console.WriteLine("Inside Swap method");
Console.WriteLine("a is {0}", a);
Console.WriteLine("b is {0}", b);
}
}
}
在此示例中,调用Swap()
方法将更改原始值。
Swap(ref a, ref b);
我们用两个参数调用该方法。 它们前面带有ref
关键字,指示我们正在通过引用传递参数。
static void Swap(ref int a, ref int b)
{
...
}
同样在方法声明中,我们使用ref
关键字来通知编译器我们接受对参数而不是值的引用。
$ dotnet run
Outside Swap method
a is 4
b is 7
Inside Swap method
a is 7
b is 4
Outside Swap method
a is 7
b is 4
在这里,我们看到Swap()
方法确实改变了变量的值。
out
关键字类似于ref
关键字。 不同之处在于,使用ref
关键字时,必须在传递变量之前对其进行初始化。 使用out
关键字,可能无法初始化。 方法定义和方法调用都必须使用out
关键字。
Program.cs
using System;
namespace OutKeyword
{
class Program
{
static void Main(string[] args)
{
int val;
SetValue(out val);
Console.WriteLine(val);
}
static void SetValue(out int i)
{
i = 12;
}
}
}
一个示例显示out
关键字的用法。
int val;
SetValue(out val);
声明了val
变量,但未初始化。 我们将变量传递给SetValue()
方法。
static void SetValue(out int i)
{
i = 12;
}
在SetValue()
方法内部,分配了一个值,该值随后会打印到控制台。
C# 方法重载
方法重载允许创建多个具有相同名称的方法,它们的输入类型彼此不同。
方法重载有什么好处? Qt5 库提供了一个很好的用法示例。 QPainter
类具有三种绘制矩形的方法。 它们的名称为drawRect()
,其参数不同。 一个引用一个浮点矩形对象,另一个引用一个整数矩形对象,最后一个引用四个参数:x
,y
,width
,height
。 如果开发 Qt 的 C++ 语言没有方法重载,则库的创建者必须将其命名为drawRectRectF()
,drawRectRect()
和drawRectXYWH()
之类的方法。 方法重载的解决方案更为优雅。
Program.cs
using System;
namespace Overloading
{
class Sum
{
public int GetSum()
{
return 0;
}
public int GetSum(int x)
{
return x;
}
public int GetSum(int x, int y)
{
return x + y;
}
}
class Program
{
static void Main()
{
var s = new Sum();
Console.WriteLine(s.GetSum());
Console.WriteLine(s.GetSum(20));
Console.WriteLine(s.GetSum(20, 30));
}
}
}
我们有三种方法GetSum()
。 它们的输入参数不同。
public int GetSum(int x)
{
return x;
}
这一个参数。
Console.WriteLine(s.GetSum());
Console.WriteLine(s.GetSum(20));
Console.WriteLine(s.GetSum(20, 30));
我们调用这三种方法。
$ dotnet run
0
20
50
这就是我们运行示例时得到的。
C# 递归
在数学和计算机科学中,递归是一种定义方法的方法,其中所定义的方法在其自己的定义内应用。 换句话说,递归方法会调用自身来完成其工作。 递归是解决许多编程任务的一种广泛使用的方法。
一个典型的例子是阶乘的计算。
Program.cs
using System;
namespace Recursion
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Factorial(6));
Console.WriteLine(Factorial(10));
}
static int Factorial(int n)
{
if (n == 0)
{
return 1;
} else
{
return n * Factorial(n-1);
}
}
}
}
在此代码示例中,我们计算两个数字的阶乘。
return n * Factorial(n-1);
在阶乘方法的主体内部,我们将阶乘方法称为经过修改的参数。 该函数调用自身。
$ dotnet run
720
3628800
这些就是结果。
C# 方法作用域
在方法内部声明的变量具有方法作用域。 名称的作用域是程序文本的区域,在该区域内,可以引用由名称声明的实体而无需使用名称限定。 在方法内部声明的变量具有方法作用域。 它也称为本地作用域。 该变量仅在此特定方法中有效。
Program.cs
using System;
namespace MethodScope
{
class Test
{
int x = 1;
public void exec1()
{
Console.WriteLine(this.x);
Console.WriteLine(x);
}
public void exec2()
{
int z = 5;
Console.WriteLine(x);
Console.WriteLine(z);
}
}
class Program
{
static void Main(string[] args)
{
var ts = new Test();
ts.exec1();
ts.exec2();
}
}
}
在前面的示例中,我们在exec1()
和exec2()
方法之外定义了x
变量。 该变量具有类作用域。 它在Test
类的定义内的任何地方都有效,例如大括号之间。
public void exec1()
{
Console.WriteLine(this.x);
Console.WriteLine(x);
}
x
变量(也称为x
字段)是一个实例变量。 因此,可以通过this
关键字进行访问。 它在exec1()
方法中也有效,并且可以用其裸名引用。 这两个语句都引用相同的变量。
public void exec2()
{
int z = 5;
Console.WriteLine(x);
Console.WriteLine(z);
}
也可以在exec2()
方法中访问 x 变量。 z
变量在exec2()
方法中定义。 它具有方法作用域。 仅在此方法中有效。
$ dotnet run
1
1
1
5
这是程序的输出。
在方法内部定义的变量具有本地/方法作用域。 如果局部变量与实例变量具有相同的名称,则它会遮盖实例变量。 通过使用this
关键字,类变量仍可在方法内部访问。
Program.cs
using System;
namespace Shadowing
{
class Test
{
int x = 1;
public void exec()
{
int x = 3;
Console.WriteLine(this.x);
Console.WriteLine(x);
}
}
class Program
{
static void Main(string[] args)
{
var ts = new Test();
ts.exec();
}
}
}
在前面的示例中,我们在exec()
方法外部和exec()
方法内部声明x
变量。 这两个变量具有相同的名称,但是它们没有冲突,因为它们存在于不同的作用域内。
Console.WriteLine(this.x);
Console.WriteLine(x);
变量的访问方式不同。 方法内定义的x
变量(也称为局部变量)仅通过其名称即可访问。 可以使用this
关键字引用实例变量。
$ dotnet run
1
3
This is the output of the program.
C# 静态方法
在没有对象实例的情况下调用静态方法。 要调用静态方法,我们使用类的名称和点运算符。 静态方法只能与静态成员变量一起使用。 静态方法通常用于表示不会随对象状态变化的数据或计算。 数学库是一个示例,其中包含用于各种计算的静态方法。 我们使用static
关键字声明一个静态方法。 如果不存在静态修饰符,则该方法称为实例方法。 我们不能在静态方法中使用this
关键字。 它只能在实例方法中使用。
Main()
方法是 C# 控制台和 GUI 应用的入口点。 在 C# 中,要求Main()
方法是静态的。 在应用启动之前,尚未创建任何对象。 要调用非静态方法,我们需要有一个对象实例。 静态方法在实例化类之前就已存在,因此将静态方法应用于主入口点。
Program.cs
using System;
namespace StaticMethod
{
class Basic
{
static int Id = 2321;
public static void ShowInfo()
{
Console.WriteLine("This is Basic class");
Console.WriteLine("The Id is: {0}", Id);
}
}
class Program
{
static void Main(string[] args)
{
Basic.ShowInfo();
}
}
}
在我们的代码示例中,我们定义了静态ShowInfo()
方法。
static int Id = 2321;
静态方法只能使用静态变量。
public static void ShowInfo()
{
Console.WriteLine("This is Basic class");
Console.WriteLine("The Id is: {0}", Id);
}
这是我们的静态ShowInfo()
方法。 它与静态 ID 成员一起使用。
Basic.ShowInfo();
要调用静态方法,我们不需要对象实例。 我们通过使用类的名称和点运算符来调用该方法。
$ dotnet run
This is Basic class
The Id is: 2321
This is the output of the example.
C# 隐藏方法
当派生类从基类继承时,它可以定义基类中已经存在的方法。 我们说隐藏了我们从中派生的类的方法。 为了明确告知编译器我们打算隐藏方法的意图,我们使用new
关键字。 没有此关键字,编译器将发出警告。
Program.cs
using System;
namespace HidingMethods
{
class Base
{
public void Info()
{
Console.WriteLine("This is Base class");
}
}
class Derived : Base
{
public new void Info()
{
base.Info();
Console.WriteLine("This is Derived class");
}
}
class Program
{
static void Main(string[] args)
{
var d = new Derived();
d.Info();
}
}
}
我们有两个类:Derived
和Base
类。 Derived
类继承自Base
类。 两者都有一种称为Info()
的方法。
class Derived : Base
{
...
}
(:)字符用于从类继承。
public new void Info()
{
base.Info();
Console.WriteLine("This is Derived class");
}
这是Derived
类中Info()
方法的实现。 我们使用new
关键字通知编译器我们正在从基类中隐藏方法。 请注意,我们仍然可以达到原始的Info()
方法。 借助base
关键字,我们也调用了Base
类的Info()
方法。
$ dotnet run
This is Base class
This is Derived class
我们已经调用了这两种方法。
C# 覆盖方法
现在,我们将引入两个新的关键字:virtual
关键字和override
关键字。 它们都是方法修饰符。 它们用于实现对象的多态行为。 virtual
关键字创建一个虚拟方法。 可以在派生类中重新定义虚拟方法。 稍后在派生类中,我们使用override
关键字重新定义相关方法。 如果派生类中的方法前面带有override
关键字,则派生类的对象将调用该方法,而不是基类方法。
Program.cs
using System;
namespace Overriding
{
class Base
{
public virtual void Info()
{
Console.WriteLine("This is Base class");
}
}
class Derived : Base
{
public override void Info()
{
Console.WriteLine("This is Derived class");
}
}
class Program
{
static void Main(string[] args)
{
Base[] objs = { new Base(), new Derived(), new Base(),
new Base(), new Base(), new Derived() };
foreach (Base obj in objs)
{
obj.Info();
}
}
}
}
我们创建Base
和Derived
对象的数组。 我们遍历数组并在所有数组上调用Info()
方法。
public virtual void Info()
{
Console.WriteLine("This is Base class");
}
这是Base
类的虚拟方法。 期望在派生类中重写它。
public override void Info()
{
Console.WriteLine("This is Derived class");
}
我们将覆盖Derived
类中的基本Info()
方法。 我们使用override
关键字。
Base[] objs = { new Base(), new Derived(), new Base(),
new Base(), new Base(), new Derived() };
在这里,我们创建Base
和Derived
对象的数组。 请注意,我们在数组声明中使用了Base
类型。 这是因为Derived
类可以继承,因此可以转换为Base
类。 相反的说法是不正确的。 将两个对象放在一个数组中的唯一方法是对所有可能的对象使用在继承层次结构中最顶层的类型。
foreach (Base obj in objs)
{
obj.Info();
}
我们遍历数组,并在数组中的所有对象上调用Info()
。
$ dotnet run
This is Base class
This is Derived class
This is Base class
This is Base class
This is Base class
This is Derived class
这是输出。
现在,将new
关键字更改为override
关键字。 再次编译该示例并运行它。
$ dotnet run
This is Base class
This is Base class
This is Base class
This is Base class
This is Base class
This is Base class
这次我们有不同的输出。
C# 本地函数
C# 7.0 引入了本地功能。 这些是在其他方法中定义的函数。
Program.cs
using System;
namespace LocalFunction
{
class Program
{
static void Main(string[] args)
{
Console.Write("Enter your name: ");
string name = Console.ReadLine();
string message = BuildMessage(name);
Console.WriteLine(message);
string BuildMessage(string value)
{
string msg = String.Format("Hello {0}!", value);
return msg;
}
}
}
}
在示例中,我们有一个局部函数BuildMessage()
,它在Main()
方法内部定义和调用。
C# 密封方法
密封方法将覆盖具有相同签名的继承虚拟方法。 密封方法也应标有倍率修饰符。 使用sealed
修饰符可防止派生类进一步覆盖该方法。 和这两个字很重要。 首先,方法必须是虚拟的。 必须稍后将其覆盖。 至此,可以将其密封。
Program.cs
using System;
namespace SealedMethods
{
class A
{
public virtual void F()
{
Console.WriteLine("A.F");
}
public virtual void G()
{
Console.WriteLine("A.G");
}
}
class B : A
{
public override void F()
{
Console.WriteLine("B.F");
}
public sealed override void G()
{
Console.WriteLine("B.G");
}
}
class C : B
{
public override void F()
{
Console.WriteLine("C.F");
}
/*public override void G()
{
Console.WriteLine("C.G");
}*/
}
class SealedMethods
{
static void Main(string[] args)
{
B b = new B();
b.F();
b.G();
C c = new C();
c.F();
c.G();
}
}
}
在前面的示例中,我们密封了类B
中的方法G()
。
public sealed override void G()
{
Console.WriteLine("B.G");
}
方法G()
会覆盖B
类的祖先中具有相同名称的方法。 它也被密封以防止进一步取代该方法。
/*public override void G()
{
Console.WriteLine("C.G");
}*/
这些行被注释,因为否则代码示例将无法编译。 编译器将给出以下错误:Program.cs
(38,30):错误 CS0239:C.G()
:无法覆盖继承的成员B.G()
,因为它是密封的
c.G();
此行将"B.G()"
打印到控制台。
$ dotnet run
B.F
B.G
C.F
B.G
This is the output.
C# 方法的表达式主体定义
方法的表达主体定义使我们能够以非常简洁,易读的形式定义方法实现。
method declaration => expression
这是一般语法。
Program.cs
using System;
namespace ExpBodyDef
{
class User
{
public string Name { get; set; }
public string Occupation { get; set; }
public override string ToString() => $"{Name} is a {Occupation}";
}
class Program
{
static void Main (string[] args)
{
var user = new User();
user.Name = "John Doe";
user.Occupation = "gardener";
Console.WriteLine(user);
}
}
}
在示例中,我们为ToString()
方法的主体提供了一个表达式主体定义。
public override string ToString() => $"{Name} is a {Occupation}";
表达式主体定义简化了语法。
在 C# 教程的这一部分中,我们介绍了方法。
C# 面向对象编程 II
在 C# 教程的这一章中,我们将继续介绍 OOP。 我们介绍了接口,多态,深层和浅层副本,密封类和异常。
C# 接口
遥控器是观众和电视之间的接口。 它是此电子设备的接口。 外交礼仪指导外交领域的所有活动。 道路规则是驾车者,骑自行车者和行人必须遵守的规则。 编程中的接口类似于前面的示例。
接口是:
- API
- 合约
对象通过其公开的方法与外界交互。 实际的实现对程序员而言并不重要,或者也可能是秘密的。 公司可能会出售图书馆,但它不想透露实际的实现情况。 程序员可能会在 GUI 工具箱的窗口上调用Maximize()
方法,但对如何实现此方法一无所知。 从这个角度来看,接口是对象与外界交互的方式,而又不会过多地暴露其内部功能。
从第二个角度来看,接口就是契约。 如果达成协议,则必须遵循。 它们用于设计应用的架构。 他们帮助组织代码。
接口是完全抽象的类型。 它们使用interface
关键字声明。 接口只能具有方法,属性,事件或索引器的签名。 所有接口成员都隐式具有公共访问权限。 接口成员不能指定访问修饰符。 接口不能具有完全实现的方法,也不能具有成员字段。 C# 类可以实现任何数量的接口。 一个接口还可以扩展任何数量的接口。 实现接口的类必须实现接口的所有方法签名。
接口用于模拟多重继承。 C# 类只能从一个类继承,但可以实现多个接口。 使用接口的多重继承与继承方法和变量无关。 它是关于继承想法或合同的,这些想法或合同由接口描述。
接口和抽象类之间有一个重要的区别。 抽象类为继承层次结构中相关的类提供部分实现。 另一方面,可以通过彼此不相关的类来实现接口。 例如,我们有两个按钮。 经典按钮和圆形按钮。 两者都继承自抽象按钮类,该类为所有按钮提供了一些通用功能。 实现类是相关的,因为它们都是按钮。 另一个示例可能具有类Database
和SignIn
。 它们彼此无关。 我们可以应用ILoggable
接口,该接口将迫使他们创建执行日志记录的方法。
C# 简单接口
以下程序使用一个简单的接口。
Program.cs
using System;
namespace SimpleInterface
{
interface IInfo
{
void DoInform();
}
class Some : IInfo
{
public void DoInform()
{
Console.WriteLine("This is Some Class");
}
}
class Program
{
static void Main(string[] args)
{
var some = new Some();
some.DoInform();
}
}
}
这是一个演示接口的简单 C# 程序。
interface IInfo
{
void DoInform();
}
这是接口IInfo
。 它具有DoInform()
方法签名。
class Some : IInfo
我们实现了IInfo
接口。 为了实现特定的接口,我们使用冒号(:)运算符。
public void DoInform()
{
Console.WriteLine("This is Some Class");
}
该类提供了DoInform()
方法的实现。
C# 多个接口
下一个示例显示了一个类如何实现多个接口。
Program.cs
using System;
namespace MultipleInterfaces
{
interface Device
{
void SwitchOn();
void SwitchOff();
}
interface Volume
{
void VolumeUp();
void VolumeDown();
}
interface Pluggable
{
void PlugIn();
void PlugOff();
}
class CellPhone : Device, Volume, Pluggable
{
public void SwitchOn()
{
Console.WriteLine("Switching on");
}
public void SwitchOff()
{
Console.WriteLine("Switching on");
}
public void VolumeUp()
{
Console.WriteLine("Volume up");
}
public void VolumeDown()
{
Console.WriteLine("Volume down");
}
public void PlugIn()
{
Console.WriteLine("Plugging In");
}
public void PlugOff()
{
Console.WriteLine("Plugging Off");
}
}
class Program
{
static void Main(string[] args)
{
var cellPhone = new CellPhone();
cellPhone.SwitchOn();
cellPhone.VolumeUp();
cellPhone.PlugIn();
}
}
}
我们有一个CellPhone
类,它从三个接口继承。
class CellPhone : Device, Volume, Pluggable
该类实现所有三个接口,并用逗号分隔。 CellPhone
类必须实现来自所有三个接口的所有方法签名。
$ dotnet run
Switching on
Volume up
Plugging In
运行程序,我们得到此输出。
C# 多接口继承
下一个示例显示接口如何从多个其他接口继承。
Program.cs
using System;
namespace InterfaceInheritance
{
interface IInfo
{
void DoInform();
}
interface IVersion
{
void GetVersion();
}
interface ILog : IInfo, IVersion
{
void DoLog();
}
class DBConnect : ILog
{
public void DoInform()
{
Console.WriteLine("This is DBConnect class");
}
public void GetVersion()
{
Console.WriteLine("Version 1.02");
}
public void DoLog()
{
Console.WriteLine("Logging");
}
public void Connect()
{
Console.WriteLine("Connecting to the database");
}
}
class Program
{
static void Main(string[] args)
{
var db = new DBConnect();
db.DoInform();
db.GetVersion();
db.DoLog();
db.Connect();
}
}
}
我们定义了三个接口。 我们可以按层次结构组织接口。
interface ILog : IInfo, IVersion
ILog
接口继承自其他两个接口。
public void DoInform()
{
Console.WriteLine("This is DBConnect class");
}
DBConnect
类实现DoInform()
方法。 该方法由该类实现的ILog
接口继承。
$ dotnet run
This is DBConnect class
Version 1.02
Logging
Connecting to the database
这是输出。
C# 多态
多态是以不同方式将运算符或函数用于不同数据输入的过程。 实际上,多态意味着如果类 B 从类 A 继承,那么它不必继承关于类 A 的所有内容。 它可以完成 A 类所做的某些事情。
通常,多态是以不同形式出现的能力。 从技术上讲,它是重新定义派生类的方法的能力。 多态与将特定实现应用于接口或更通用的基类有关。
多态是重新定义派生类的方法的能力。
Program.cs
using System;
namespace Polymorphism
{
abstract class Shape
{
protected int x;
protected int y;
public abstract int Area();
}
class Rectangle : Shape
{
public Rectangle(int x, int y)
{
this.x = x;
this.y = y;
}
public override int Area()
{
return this.x * this.y;
}
}
class Square : Shape
{
public Square(int x)
{
this.x = x;
}
public override int Area()
{
return this.x * this.x;
}
}
class Program
{
static void Main(string[] args)
{
Shape[] shapes = { new Square(5), new Rectangle(9, 4), new Square(12) };
foreach (Shape shape in shapes)
{
Console.WriteLine(shape.Area());
}
}
}
}
在上面的程序中,我们有一个抽象的Shape
类。 此类演变为两个后代类别:Rectangle
和Square
。 两者都提供了自己的Area()
方法实现。 多态为 OOP 系统带来了灵活性和可伸缩性。
public override int Area()
{
return this.x * this.y;
}
...
public override int Area()
{
return this.x * this.x;
}
Rectangle
和Square
类具有Area()
方法的自己的实现。
Shape[] shapes = { new Square(5), new Rectangle(9, 4), new Square(12) };
我们创建三个形状的数组。
foreach (Shape shape in shapes)
{
Console.WriteLine(shape.Area());
}
我们遍历每个形状,并在其上调用Area()
方法。 编译器为每种形状调用正确的方法。 这就是多态的本质。
C# 密封类
sealed
关键字用于防止意外地从类派生。 密封类不能是抽象类。
Program.cs
using System;
namespace DerivedMath
{
sealed class Math
{
public static double GetPI()
{
return 3.141592;
}
}
class Derived : Math
{
public void Say()
{
Console.WriteLine("Derived class");
}
}
class Program
{
static void Main(string[] args)
{
var dm = new Derived();
dm.Say();
}
}
}
在上面的程序中,我们有一个Math
基类。 该类的唯一目的是为程序员提供一些有用的方法和常量。 (出于简单起见,在我们的案例中,我们只有一种方法。)它不是从继承而创建的。 为了防止不知情的其他程序员从此类中派生,创建者创建了sealed
类。 如果尝试编译该程序,则会出现以下错误:'Derived'不能从密封类'Math'派生。
C# 深层副本与浅层副本
数据复制是编程中的重要任务。 对象是 OOP 中的复合数据类型。 对象中的成员字段可以按值或按引用存储。 可以以两种方式执行复制。
浅表副本将所有值和引用复制到新实例中。 引用所指向的数据不会被复制; 仅指针被复制。 新的引用指向原始对象。 对引用成员的任何更改都会影响两个对象。
深层副本将所有值复制到新实例中。 如果成员存储为引用,则深层副本将对正在引用的数据执行深层副本。 创建一个引用对象的新副本。 并存储指向新创建对象的指针。 对这些引用对象的任何更改都不会影响该对象的其他副本。 深拷贝是完全复制的对象。
如果成员字段是值类型,则将对该字段进行逐位复制。 如果该字段是引用类型,则复制引用,但不是复制引用的对象。 因此,原始对象中的引用和克隆对象中的引用指向同一对象。 (来自 programmingcorner.blogspot.com 的明确解释)
C# 浅表复制
以下程序执行浅表复制。
Program.cs
using System;
namespace ShallowCopy
{
class Color
{
public int red;
public int green;
public int blue;
public Color(int red, int green, int blue)
{
this.red = red;
this.green = green;
this.blue = blue;
}
}
class MyObject : ICloneable
{
public int id;
public string size;
public Color col;
public MyObject(int id, string size, Color col)
{
this.id = id;
this.size = size;
this.col = col;
}
public object Clone()
{
return new MyObject(this.id, this.size, this.col);
}
public override string ToString()
{
var s = String.Format("id: {0}, size: {1}, color:({2}, {3}, {4})",
this.id, this.size, this.col.red, this.col.green, this.col.blue);
return s;
}
}
class Program
{
static void Main(string[] args)
{
var col = new Color(23, 42, 223);
var obj1 = new MyObject(23, "small", col);
var obj2 = (MyObject) obj1.Clone();
obj2.id += 1;
obj2.size = "big";
obj2.col.red = 255;
Console.WriteLine(obj1);
Console.WriteLine(obj2);
}
}
}
这是一个浅表副本的示例。 我们定义了两个自定义对象:MyObject
和Color
。 MyObject
对象将引用 Color 对象。
class MyObject : ICloneable
我们应该为要克隆的对象实现ICloneable
接口。
public object Clone()
{
return new MyObject(this.id, this.size, this.col);
}
ICloneable
接口迫使我们创建Clone()
方法。 此方法返回具有复制值的新对象。
var col = new Color(23, 42, 223);
我们创建Color
对象的实例。
var obj1 = new MyObject(23, "small", col);
创建MyObject
类的实例。 Color
对象的实例传递给构造器。
var obj2 = (MyObject) obj1.Clone();
我们创建obj1
对象的浅表副本,并将其分配给obj2
变量。 Clone()
方法返回Object
,我们期望MyObject
。 这就是我们进行显式转换的原因。
obj2.id += 1;
obj2.size = "big";
obj2.col.red = 255;
在这里,我们修改复制对象的成员字段。 我们增加id
,将size
更改为"big"
,然后更改颜色对象的红色部分。
Console.WriteLine(obj1);
Console.WriteLine(obj2);
Console.WriteLine()
方法调用obj2
对象的ToString()
方法,该方法返回对象的字符串表示形式。
$ dotnet run
id: 23, size: small, color:(255, 42, 223)
id: 24, size: big, color:(255, 42, 223)
我们可以看到 ID 是不同的(23 对 24)。 大小不同(small
与big
)。 但是,这两个实例的颜色对象的红色部分相同(255)。 更改克隆对象的成员值(id
,size
)不会影响原始对象。 更改引用对象(col
)的成员也影响了原始对象。 换句话说,两个对象都引用内存中的同一颜色对象。
C# 深层复制
要更改此行为,我们接下来将做一个深层复制。
Program.cs
using System;
namespace DeepCopy
{
class Color : ICloneable
{
public int red;
public int green;
public int blue;
public Color(int red, int green, int blue)
{
this.red = red;
this.green = green;
this.blue = blue;
}
public object Clone()
{
return new Color(this.red, this.green, this.blue);
}
}
class MyObject : ICloneable
{
public int id;
public string size;
public Color col;
public MyObject(int id, string size, Color col)
{
this.id = id;
this.size = size;
this.col = col;
}
public object Clone()
{
return new MyObject(this.id, this.size,
(Color)this.col.Clone());
}
public override string ToString()
{
var s = String.Format("id: {0}, size: {1}, color:({2}, {3}, {4})",
this.id, this.size, this.col.red, this.col.green, this.col.blue);
return s;
}
}
class Program
{
static void Main(string[] args)
{
var col = new Color(23, 42, 223);
var obj1 = new MyObject(23, "small", col);
var obj2 = (MyObject) obj1.Clone();
obj2.id += 1;
obj2.size = "big";
obj2.col.red = 255;
Console.WriteLine(obj1);
Console.WriteLine(obj2);
}
}
}
在此程序中,我们对对象执行深层复制。
class Color : ICloneable
现在,Color
类实现了ICloneable
接口。
public object Clone()
{
return new Color(this.red, this.green, this.blue);
}
我们也为Color
类提供了Clone()
方法。 这有助于创建引用对象的副本。
public object Clone()
{
return new MyObject(this.id, this.size,
(Color) this.col.Clone());
}
当我们克隆MyObject
时,我们根据col
引用类型调用Clone()
方法。 这样,我们也可以获得颜色值的副本。
$ dotnet run
id: 23, size: small, color:(23, 42, 223)
id: 24, size: big, color:(255, 42, 223)
现在,所引用的Color
对象的红色部分不再相同。 原始对象保留了其先前的值(23)。
C# 异常
异常是为处理异常的发生而设计的,这些特殊情况会改变程序执行的正常流程。 引发或引发异常。
在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存文件。 当我们的应用尝试连接到站点时,互联网连接可能会断开。 所有这些都可能导致我们的应用崩溃。 程序员有责任处理可以预期的错误。
try
,catch
和finally
关键字用于处理异常。
Program.cs
using System;
namespace DivisionByZero
{
class Program
{
static void Main(string[] args)
{
int x = 100;
int y = 0;
int z;
try
{
z = x / y;
}
catch (ArithmeticException e)
{
Console.WriteLine("An exception occurred");
Console.WriteLine(e.Message);
}
}
}
}
在上面的程序中,我们有意将数字除以零。 这会导致错误。
try
{
z = x / y;
}
容易出错的语句放置在try
块中。
catch (ArithmeticException e)
{
Console.WriteLine("An exception occurred");
Console.WriteLine(e.Message);
}
异常类型跟随catch
关键字。 在我们的情况下,我们有一个ArithmeticException
。 由于算术,转换或转换操作中的错误而引发此异常。 发生错误时,将执行catch
关键字之后的语句。 发生异常时,将创建一个异常对象。 从该对象中,我们获得Message
属性并将其打印到控制台。
$ dotnet run
An exception occurred
Attempted to divide by zero.
代码示例的输出。
C# 未捕获的异常
当前上下文中任何未捕获的异常都会传播到更高的上下文,并寻找适当的catch
块来处理它。 如果找不到任何合适的catch
块,则 .NET 运行时的默认机制将终止整个程序的执行。
Program.cs
using System;
namespace UcaughtException
{
class Program
{
static void Main(string[] args)
{
int x = 100;
int y = 0;
int z = x / y;
Console.WriteLine(z);
}
}
}
在此程序中,我们除以零。 没有自定义异常处理。
$ dotnet run
Unhandled Exception: System.DivideByZeroException: Division by zero
at UncaughtException.Main () [0x00000]
C# 编译器给出了以上错误消息。
C# IOException
发生 I/O 错误时,将抛出IOException
。 在下面的示例中,我们读取文件的内容。
Program.cs
using System;
using System.IO;
namespace ReadFile
{
class Program
{
static void Main(string[] args)
{
var fs = new FileStream("langs.txt", FileMode.OpenOrCreate);
try
{
var sr = new StreamReader(fs);
string line;
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
catch (IOException e)
{
Console.WriteLine("IO Error");
Console.WriteLine(e.Message);
}
finally
{
Console.WriteLine("Inside finally block");
if (fs != null)
{
fs.Close();
}
}
}
}
}
始终执行finally
关键字之后的语句。 它通常用于清理任务,例如关闭文件或清除缓冲区。
} catch (IOException e)
{
Console.WriteLine("IO Error");
Console.WriteLine(e.Message);
}
在这种情况下,我们捕获了特定的IOException
异常。
} finally
{
Console.WriteLine("Inside finally block");
if (fs != null)
{
fs.Close();
}
}
这些行确保关闭文件处理器。
$ cat langs.txt
C#
Java
Python
Ruby
PHP
JavaScript
这些是langs.txt
文件的内容。
$ dotnet run
C#
Java
Python
Ruby
PHP
JavaScript
Inside finally block
这是程序的输出。
我们使用cat
命令和程序输出显示langs
文件的内容。
C# 多个异常
我们经常需要处理多个异常。
Program.cs
using System;
using System.IO;
namespace MultipleExceptions
{
class Program
{
static void Main(string[] args)
{
int x;
int y;
double z;
try
{
Console.Write("Enter first number: ");
x = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter second number: ");
y = Convert.ToInt32(Console.ReadLine());
z = x / y;
Console.WriteLine("Result: {0:N} / {1:N} = {2:N}", x, y, z);
}
catch (DivideByZeroException e)
{
Console.WriteLine("Cannot divide by zero");
Console.WriteLine(e.Message);
}
catch (FormatException e)
{
Console.WriteLine("Wrong format of number.");
Console.WriteLine(e.Message);
}
}
}
}
在此示例中,我们捕获了各种异常。 请注意,更具体的异常应先于一般的异常。 我们从控制台读取两个数字,并检查零除错误和数字格式错误。
$ dotnet run
Enter first number: we
Wrong format of number.
Input string was not in a correct format.
运行示例,我们得到了这个结果。
C# 自定义异常
定制异常是从System.Exception
类派生的用户定义的异常类。
Program.cs
using System;
namespace CustomException
{
class BigValueException : Exception
{
public BigValueException(string msg) : base(msg) { }
}
class Program
{
static void Main(string[] args)
{
int x = 340004;
const int LIMIT = 333;
try
{
if (x > LIMIT)
{
throw new BigValueException("Exceeded the maximum value");
}
}
catch (BigValueException e)
{
Console.WriteLine(e.Message);
}
}
}
}
我们假定存在无法处理大量数字的情况。
class BigValueException : Exception
我们有一个BigValueException
类。 该类派生自内置的Exception
类。
const int LIMIT = 333;
大于此常数的数字在我们的程序中被视为big
。
public BigValueException(string msg) : base(msg) {}
在构造器内部,我们称为父级的构造器。 我们将消息传递给父级。
if (x > LIMIT)
{
throw new BigValueException("Exceeded the maximum value");
}
如果该值大于限制,则抛出自定义异常。 我们给异常消息Exceeded the maximum value
。
} catch (BigValueException e)
{
Console.WriteLine(e.Message);
}
我们捕获到异常并将其消息打印到控制台。
$ dotnet run
Exceeded the maximum value
This is the output of the program.
在 C# 教程的这一部分中,我们继续讨论 C# 中的面向对象编程。
C# 属性
在 C# 教程的这一部分中,我们将讨论属性。
属性是一种特殊的类成员。 我们使用预定义的设置和获取方法来访问和修改它们。 属性读取和写入会转换为获取和设置方法调用。 与使用自定义方法调用(例如object.GetName()
)相比,使用字段符号(例如object.Name
)访问变量更容易。 但是,就属性而言,我们仍然具有封装和信息隐藏的优势。 换句话说,属性可以保护数据免受外界干扰,同时可以方便地进行现场访问。
接口可以具有属性,但不能具有字段。
属性可以是读写的(它们既有获取和设置访问器),也可以是只读的(它们只有获取访问器)或只写(它们只有设置访问器)。
Program.cs
using System;
namespace SimpleProperties
{
class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
class Program
{
static void Main(string[] args)
{
var p = new Person();
p.Name = "Jane";
Console.WriteLine(p.Name);
}
}
}
我们有一个具有一个属性的简单Person
类。
public string Name
{
...
}
我们有一个称为Name
的属性。 它看起来像一个常规方法声明。 不同之处在于,它具有称为get
和set
的特定访问器。
get { return _name; }
set { _name = value; }
get
属性访问器用于返回属性值,set
访问器用于分配新值。 value
关键字用于定义由设置索引器分配的值。
var p = new Person();
p.Name = "Jane";
Console.WriteLine(p.Name);
我们创建Person
类的实例。 我们使用字段符号访问成员字段。
$ dotnet run
Jane
这是该计划的结果。
C# 只读属性
可以创建只读属性。 为了创建一个只读属性,我们省略了 set 访问器,在实现中仅提供了 get 访问器。
Program.cs
using System;
namespace Readonly
{
class Person
{
private string _name = "Jane";
public string Name
{
get { return _name; }
}
}
class Program
{
static void Main(string[] args)
{
var p = new Person();
// p.Name = "Beky";
Console.WriteLine(p.Name);
}
}
}
在前面的示例中,我们演示了只读属性的使用。
private string _name = "Jane";
我们立即初始化成员,因为以后不可能。
public string Name
{
get { return _name; }
}
通过仅提供一个 get 访问器,使该属性为只读。
// p.Name = "Beky";
现在此行已注释。 我们无法更改属性。 如果我们取消注释该行,则 C# 编译器将发出以下错误:Program.cs(21,13): error CS0200: Property or indexer 'Person.Name' cannot be assigned to -- it is read only
。
C# 自动实现的属性
C# 具有自动实现或自动属性。 在软件项目中,有许多简单属性只能设置或获取一些简单值。 为了简化编程并简化代码,创建了自动属性。 注意,我们不能在所有情况下都使用自动属性。 仅适用于简单的。
Program.cs
using System;
namespace Autoimplemented
{
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main(string[] args)
{
var p = new Person();
p.Name = "Jane";
p.Age = 17;
Console.WriteLine($"{p.Name} is {p.Age} years old");
}
}
}
该代码要短得多。 我们有一个Person
类,其中有两个属性:Name
和Age
。
public string Name { get; set; }
public int Age { get; set; }
在这里,我们有两个自动属性。 没有访问器的实现,也没有成员字段。 编译器将为我们完成其余的工作。
var p = new Person();
p.Name = "Jane";
p.Age = 17;
Console.WriteLine($"{p.Name} is {p.Age} years old");
我们通常照常使用这些属性。
$ dotnet run
Jane is 17 years old
这是示例的输出。
表达式主体定义
从 C# 7.0 开始,可以使用表达式主体定义简化属性。 表达式主体定义由=>
符号组成,后跟要分配给该属性或从该属性检索的表达式。
Program.cs
using System;
namespace ExpBodyDef
{
class User
{
string name;
string occupation;
public User(string name, string occupation)
{
this.name = name;
this.occupation = occupation;
}
public string Name
{
get => name;
set => name = value;
}
public string Occupation
{
get => occupation;
set => occupation = value;
}
}
class Program
{
static void Main(string[] args)
{
var u = new User("John Doe", "gardener");
Console.WriteLine($"{u.Name} is a {u.Occupation}");
}
}
}
在示例中,我们使用表达式主体定义来定义User
类的属性。
$ dotnet run
John Doe is a gardener
这是输出。
其他注意事项
我们可以使用public
,private
或protected
等访问修饰符标记属性。 属性也可以是static
,abstract
,virtual
和sealed
。 它们的用法与常规方法相同。
Program.cs
using System;
namespace OtherNotes
{
class Base
{
protected string _name = "Base class";
public virtual string Name
{
set { _name = value; }
get { return _name; }
}
}
class Derived : Base
{
protected new string _name = "Derived class";
public override string Name
{
set { _name = value; }
get { return _name; }
}
}
class Program
{
static void Main(string[] args)
{
var bs = new Base();
var dr = new Derived();
Console.WriteLine(bs.Name);
Console.WriteLine(dr.Name);
}
}
}
在前面的示例中,我们定义了一个虚拟属性,并在Derived
类中将其覆盖。
public virtual string Name
{
set { _name = value; }
get { return _name; }
}
名称属性用virtual
关键字标记。
protected new string _name = "Derived class";
我们将成员隐藏在Derived
类中。 为了消除编译器警告,我们使用new
关键字。
public override string Name
{
set { _name = value; }
get { return _name; }
}
在这里,我们重写了Base
类的Name
属性。
本章介绍了 C# 属性。 我们描述了属性并显示了如何实现它们。 我们提到了自动属性和只读属性。
C# 结构
C# 教程的这一部分涵盖结构。
C# 结构定义
结构是值类型。 该类型由struct
关键字定义。 结构与类非常相似。 它们在某些方面有所不同。 结构旨在表示轻量级对象,例如Point
,Rectangle
,Color
等。 在许多情况下,结构可能比类更有效。 结构是值类型,并在栈上创建。 注意,像int
,bool
,float
之类的原始数据类型在技术上都是struct
类型。
所有struct
类型都继承自System.ValueType
,并且继承自System.Object
。 结构从来都不是抽象的,它们总是被隐式密封的。 因此结构类型不支持继承。 因此,不能将struct
数据成员声明为受保护的。 struct
定义不允许使用抽象修饰符和密封修饰符。 不允许struct
声明无参数的构造器。
结构还可以包含构造器,常量,字段,方法,属性,索引器,运算符,事件和嵌套类型。 但是,如果我们需要实现更多这些功能,则可以考虑使用一个类。 结构可以实现接口。 struct
可以用作nullable
类型,并且可以分配为空值。
简单结构示例
以下示例创建一个简单的结构。
Program.cs
using System;
namespace SimpleStructure
{
public struct Point
{
private int x;
private int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public override string ToString()
{
return String.Format("Point x:{0}, y:{1}", x, y);
}
}
class Program
{
static void Main(string[] args)
{
var p = new Point(2, 5);
Console.WriteLine(p);
}
}
}
该示例创建一个Point
结构。 这个点也可以用一个类来表示,但是有了struct
,我们的效率更高了。 特别是如果我们处理了很多问题。
public struct Point
{
...
}
该结构用struct
关键字声明。
public override string ToString()
{
return String.Format("Point x:{0}, y:{1}", x, y);
}
struct
类型不支持继承。 但是,我们可以对方法使用override
关键字,struct
类型隐式地从中继承。 ToString()
方法就是这种情况。
var p = new Point(2, 5);
Console.WriteLine(p);
我们创建Point
结构,并在其上调用ToString()
方法。
$ dotnet run
Point x:2, y:5
这是示例的输出。
没有new
关键字
可以创建不带new
关键字的struct
类型的实例。
Program.cs
using System;
namespace NoNewKeyword
{
public struct Person
{
public string name;
public int age;
}
class Program
{
static void Main(string[] args)
{
Person p;
p.name = "Jane";
p.age = 17;
Console.WriteLine("{0} is {1} years old",
p.name, p.age);
}
}
}
我们有一个Person
结构,其中有两个公共成员。
Person p;
首先,我们声明一个Person
结构。
p.name = "Jane";
p.age = 17;
稍后我们用一些数据初始化结构。
$ dotnet run
Jane is 17 years old
这是程序的输出。
C# 结构是值类型
结构类型是值类型。 它们是在栈上创建的。 创建值类型时,仅在内存中分配了一个空间来存储值。 值类型的分配将复制该值。
Program.cs
using System;
namespace ValueTypes
{
public struct Person
{
public Person(string name, int age) : this()
{
this.Name = name;
this.Age = age;
}
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return String.Format("{0} is {1} years old", Name, Age);
}
}
class Program
{
static void Main(string[] args)
{
var p1 = new Person("Beky", 18);
var p2 = p1;
Console.WriteLine(p2);
p2.Name = "Jane";
p2.Age = 17;
Console.WriteLine(p2);
Console.WriteLine(p1);
}
}
}
我们有一个带有两个数据成员的Person
结构。 我们有两个参数的构造器,我们也使用自动属性。
public string Name { get; set; }
public int Age { get; set; }
可以在struct
类型中使用自动属性。
var p1 = new Person("Beky", 18);
var p2 = p1;
在这里,我们创建一个struct
。 然后将创建的struct
分配给另一个struct
。 我们创建该结构的副本。
p2.Name = "Jane";
p2.Age = 17;
我们更改第二个结构的数据。 第一个不受影响,因为我们正在处理原始struct
类型的副本。
$ dotnet run
Beky is 18 years old
Jane is 17 years old
Beky is 18 years old
This is the output of the program.
基本类型是结构
像int
,float
或bool
之类的原始数据类型是内部结构。 这不同于 C++ 或 Java 之类的语言。
Program.cs
using System;
public class PrimitiveTypes
{
static void Main()
{
float x = 12.3f;
int y = 34;
bool z = false;
Console.WriteLine(x.GetType());
Console.WriteLine(y.GetType());
Console.WriteLine(z.GetType());
}
}
我们有三个变量:float
,int
和bool
。 我们对它们每个调用GetType()
方法。
Console.WriteLine(x.GetType());
我们在浮点值上调用GetType()
方法。 每个结构都隐式继承自包含GetType()
方法的System.ValueType
类。
$ dotnet run
System.Single
System.Int32
System.Boolean
这是示例的输出。 我们可以在文档中查找这些类型是结构。
在 C# 教程的这一部分中,我们介绍了结构。
C# 委托
C# 教程的这一部分专门针对委托。
委托是.NET Framework 使用的一种类型安全的函数指针。 委托通常用于实现回调和事件监听器。 委托无需了解其使用的方法类的任何知识。
委托是引用类型。 但是委托不是引用对象,而是引用方法。
在以下情况下使用委托:
- 事件处理器
- 回调
- LINQ
- 设计模式的实现
委托没有什么可以用常规方法完成的。 之所以使用委托,是因为它们带来了许多优点。 它们提高了应用和代码重用的灵活性。 像接口一样,委托使我们能够解耦和泛化我们的代码。 委托还允许将方法作为参数传递。 当我们需要确定在运行时调用哪种方法时,可以使用委托。 最后,委托提供了一种无需对子类进行子类化就可以对它的行为进行专门化的方法。 类可能具有复杂的泛型行为,但仍应专门化。 类是通过继承或通过委托来专用的。
C# 使用委托
我们将有一些简单的示例显示如何使用委托。
Program.cs
using System;
namespace SimpleDelegate
{
delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
var md = new MyDelegate(MyCallback);
md();
}
static void MyCallback()
{
Console.WriteLine("Calling callback");
}
}
}
我们声明一个委托,创建该委托的实例并调用它。
delegate void MyDelegate();
这是我们的委托声明。 它不返回任何值,不接受任何参数。
var md = new MyDelegate(MyCallback);
我们创建委托的实例。 调用时,委托将调用静态Callback()
方法。
md();
我们打电话给委托。
$ dotnet run
Calling callback
这是输出。
我们可以使用不同的语法来创建和使用委托。
Program.cs
using System;
namespace SimpleDelegate2
{
delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
MyDelegate del = MyCallback;
del();
}
static void MyCallback()
{
Console.WriteLine("Calling callback");
}
}
}
创建委托的实例时,我们可以保存一些类型。
MyDelegate del = MyCallback;
这是创建委托的另一种方法。 我们直接指向方法名称。
C# 委托指向不同的方法
委托可以随着时间指向不同的方法。
Program.cs
using System;
namespace DifferentMethods
{
public delegate void NameDelegate(string msg);
public class Person
{
public string firstName;
public string secondName;
public Person(string firstName, string secondName)
{
this.firstName = firstName;
this.secondName = secondName;
}
public void ShowFirstName(string msg)
{
Console.WriteLine(msg + this.firstName);
}
public void ShowSecondName(string msg)
{
Console.WriteLine(msg + this.secondName);
}
}
class Program
{
public static void Main()
{
var per = new Person("Fabius", "Maximus");
var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");
nDelegate = new NameDelegate(per.ShowSecondName);
nDelegate("Call 2: ");
}
}
}
在此示例中,我们只有一名委托。 该委托用于指向Person
类的两个方法。 方法与委托一起调用。
public delegate void NameDelegate(string msg);
使用delegate
关键字创建委托。 委托签名必须与委托调用的方法的签名匹配。
var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");
我们创建一个新委托的实例,该实例指向ShowFirstName()
方法。 稍后我们通过委托调用该方法。
$ dotnet run
Call 1: Fabius
Call 2: Maximus
这两个名称都是通过委托打印的。
C# 多播委托
多播委托是一个拥有对多个方法的引用的委托。 多播委托必须仅包含返回void
的方法,否则将存在运行时异常。
Program.cs
using System;
namespace MulticastDelegate
{
delegate void MyDelegate(int x, int y);
public class Oper
{
public static void Add(int x, int y)
{
Console.WriteLine("{0} + {1} = {2}", x, y, x + y);
}
public static void Sub(int x, int y)
{
Console.WriteLine("{0} - {1} = {2}", x, y, x - y);
}
}
class Program
{
static void Main()
{
var del = new MyDelegate(Oper.Add);
del += new MyDelegate(Oper.Sub);
del(6, 4);
del -= new MyDelegate(Oper.Sub);
del(2, 8);
}
}
}
这是一个多播委托的示例。
delegate void MyDelegate(int x, int y);
我们的委托接受两个参数。 我们有一个Oper
类,它具有两个静态方法。 一个将两个值相加,另一个将两个值相减。
var del = new MyDelegate(Oper.Add);
我们创建委托的实例。 委托指向Oper
类的静态Add()
方法。
del += new MyDelegate(Oper.Sub);
del(6, 4);
我们将另一个方法插入到现有的委托实例中。 委托的第一次调用将调用两个方法。
del -= new MyDelegate(Oper.Sub);
del(2, 8);
我们从委托中删除一种方法。 委托的第二次调用仅调用一种方法。
$ dotnet run
6 + 4 = 10
6 - 4 = 2
2 + 8 = 10
这是程序的输出。
C# 匿名方法
可以对委托使用匿名方法。
Program.cs
using System;
namespace Anonymous
{
delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
MyDelegate del = delegate
{
Console.WriteLine("Anonymous method");
};
del();
}
}
}
当将匿名方法与委托一起使用时,我们可以省略方法声明。 该方法没有名称,只能通过委托来调用。
MyDelegate del = delegate
{
Console.WriteLine("Anonymous method");
};
在这里,我们创建一个指向匿名方法的委托。 匿名方法的主体用{}
字符括起来,但是没有名称。
C# 委托作为方法参数
委托可以用作方法参数。
Program.cs
using System;
namespace MethodParameters
{
delegate int Arithm(int x, int y);
class Program
{
static void Main()
{
DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);
}
static void DoOperation(int x, int y, Arithm del)
{
int z = del(x, y);
Console.WriteLine(z);
}
static int Multiply(int x, int y)
{
return x * y;
}
static int Divide(int x, int y)
{
return x / y;
}
}
}
我们有一个DoOperation()
方法,该方法将一个委托作为参数。
delegate int Arithm(int x, int y);
这是一个委托声明。
static void DoOperation(int x, int y, Arithm del)
{
int z = del(x, y);
Console.WriteLine(z);
}
这是DoOperation()
方法的实现。 第三个参数是委托。 DoOperation()
方法调用一个方法,该方法作为第三个参数传递给它。
DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);
我们称为DoOperation()
方法。 我们传递两个值和一个方法给它。 我们对这两个值的处理方式取决于我们通过的方法。 这就是使用委托所带来的灵活性。
$ dotnet run
20
5
This is the output.
C# 事件
事件是由某些操作触发的消息。 单击按钮或滴答滴答即是这种动作。 触发事件的对象称为发送者,而接收事件的对象称为接收者。
按照约定,.NET Framework 中的事件委托具有两个参数:引发事件的源和事件的数据。
Program.cs
using System;
namespace SimpleEvent
{
public delegate void OnFiveHandler(object sender, EventArgs e);
class FEvent
{
public event OnFiveHandler FiveEvent;
public void OnFiveEvent()
{
if (FiveEvent != null)
{
FiveEvent(this, EventArgs.Empty);
}
}
}
class Program
{
static void Main()
{
var fe = new FEvent();
fe.FiveEvent += new OnFiveHandler(Callback);
var random = new Random();
for (int i = 0; i < 10; i++)
{
int rn = random.Next(6);
Console.WriteLine(rn);
if (rn == 5)
{
fe.OnFiveEvent();
}
}
}
public static void Callback(object sender, EventArgs e)
{
Console.WriteLine("Five Event occurred");
}
}
}
我们有一个简单的示例,可以在其中创建和启动事件。 生成一个随机数。 如果数字等于 5,则会生成FiveEvent
事件。
public event OnFiveHandler FiveEvent;
使用event
关键字声明事件。
fe.FiveEvent += new OnFiveHandler(Callback);
在这里,我们将名为FiveEvent
的事件插入到Callback()
方法中。 换句话说,如果触发了ValueFive
事件,则将执行Callback()
方法。
public void OnFiveEvent()
{
if(FiveEvent != null)
{
FiveEvent(this, EventArgs.Empty);
}
}
当随机数等于 5 时,我们调用OnFiveEvent()
方法。 在这种方法中,我们引发了FiveEvent
事件。 此事件不包含任何参数。
$ dotnet run
1
1
5
Five Event occurred
1
1
4
1
2
4
5
Five Event occurred
这是一个示例输出。
C# 复杂事件示例
接下来,我们有一个更复杂的示例。 这次,我们将通过生成的事件发送一些数据。
Program.cs
using System;
namespace ComplexEvent
{
public delegate void OnFiveHandler(object sender, FiveEventArgs e);
public class FiveEventArgs : EventArgs
{
public int count;
public DateTime time;
public FiveEventArgs(int count, DateTime time)
{
this.count = count;
this.time = time;
}
}
public class FEvent
{
public event OnFiveHandler FiveEvent;
public void OnFiveEvent(FiveEventArgs e)
{
FiveEvent(this, e);
}
}
public class RandomEventGenerator
{
public void Generate()
{
int count = 0;
FiveEventArgs args;
var fe = new FEvent();
fe.FiveEvent += new OnFiveHandler(Callback);
var random = new Random();
for (int i = 0; i < 10; i++)
{
int rn = random.Next(6);
Console.WriteLine(rn);
if (rn == 5)
{
count++;
args = new FiveEventArgs(count, DateTime.Now);
fe.OnFiveEvent(args);
}
}
}
public void Callback(object sender, FiveEventArgs e)
{
Console.WriteLine("Five event {0} occurred at {1}",
e.count, e.time);
}
}
class Program
{
static void Main()
{
var reg = new RandomEventGenerator();
reg.Generate();
}
}
}
我们有四个类。 FiveEventArgs
带有事件对象的一些数据。 FEvent
类封装了事件对象。 RandomEventGenerator
类负责生成随机数。 它是事件发送者。 最后,ComplexEvent
是主要的应用类。
public class FiveEventArgs : EventArgs
{
public int count;
public DateTime time;
...
FiveEventArgs
在事件对象内部传送数据。 它继承自EventArgs
基类。 计数和时间成员是将被初始化并随事件一起携带的数据。
if (rn == 5)
{
count++;
args = new FiveEventArgs(count, DateTime.Now);
fe.OnFiveEvent(args);
}
如果生成的随机数等于 5,我们用当前计数和DateTime
值实例化FiveEventArgs
类。 count
变量对生成此事件的次数进行计数。 DateTime
值保存事件生成的时间。
$ dotnet run
4
4
1
3
3
3
2
5
Five event 1 occurred at 10/22/2019 2:13:10 PM
3
0
这是程序的示例输出。
C# 预定义的委托
.NET 框架具有多个内置的委托,这些委托减少了所需的输入并简化了开发者的编程工作。
C# 动作委托
操作委托封装了没有参数且不返回值的方法。
Program.cs
using System;
namespace ActionDelegate
{
class Program
{
static void Main()
{
Action act = ShowMessage;
act();
}
static void ShowMessage()
{
Console.WriteLine("C# language");
}
}
}
使用预定义的委托可以进一步简化编程。 我们不需要声明委托类型。
Action act = ShowMessage;
act();
我们实例化一个动作委托。 委托指向ShowMessage()
方法。 调用委托时,将执行ShowMessage()
方法。
有多种类型的动作委托。 例如,Action<T>
委托封装了一个采用单个参数且不返回值的方法。
Program.cs
using System;
namespace ActionDelegate2
{
class Program
{
static void Main()
{
Action<string> act = ShowMessage;
act("C# language");
}
static void ShowMessage(string message)
{
Console.WriteLine(message);
}
}
}
我们修改前面的示例,以使用带有一个参数的动作委托。
Action<string> act = ShowMessage;
act("C# language");
我们创建Action<T>
委托的实例,并使用一个参数对其进行调用。
C# 谓词委托
谓词是一种返回true
或false
的方法。 谓词委托是对谓词的引用。 谓词对于过滤值列表非常有用。
Program.cs
using System;
using System.Collections.Generic;
namespace PredicateDelegate
{
class Program
{
static void Main()
{
List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };
Predicate<int> myPred = greaterThanThree;
List<int> vals2 = vals.FindAll(myPred);
foreach (int i in vals2)
{
Console.WriteLine(i);
}
}
static bool greaterThanThree(int x)
{
return x > 3;
}
}
}
我们有一个整数值列表。 我们要过滤所有大于三的数字。 为此,我们使用谓词委托。
List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };
这是整数值的一般列表。
Predicate<int> myPred = greaterThanThree;
我们创建一个谓词委托的实例。 委托指向谓词,这是一种返回true
或false
的特殊方法。
List<int> vals2 = vals.FindAll(myPred);
FindAll()
方法检索与指定谓词定义的条件匹配的所有元素。
static bool greaterThanThree(int x)
{
return x > 3;
}
对于大于三个的所有值,谓词返回true
。
C# 教程的这一部分专用于委托。
命名空间
在 C# 教程的这一部分中,我们介绍了名称空间。
命名空间用于在最高逻辑级别上组织代码。 他们对公开给其他程序和应用的编程元素进行分类和呈现。 在一个名称空间内,我们可以声明另一个名称空间,一个类,一个接口,一个结构,一个枚举或一个委托。
我们无法定义诸如属性,变量和事件之类的项目。 这些项目必须在诸如结构或类之类的容器中声明。 当使用大量对象(例如类库)时,命名空间可防止歧义并简化引用。
命名空间在程序集中组织对象。 程序集是 CLR 应用的可重用,可版本控制和自描述的构建块。 程序集可以包含多个名称空间。 命名空间可以包含其他命名空间。 程序集提供了物理代码分组的基本单元。 命名空间提供了逻辑代码分组的基本单位。
C# 创建名称空间
namespace
关键字用于在 C# 中声明一个名称空间。 命名空间的名称必须是有效的 C# 标识符名称。 命名空间用.
运算符定界。 using
指令消除了为每个类指定名称空间名称的要求。
C# 内置名称空间
内置库在名称空间内组织。
Program.cs
public class Program
{
static void Main()
{
System.Console.WriteLine("Simple namespace example");
}
}
例如,Console
类在System
名称空间中可用。 要调用Console
类的静态WriteLine()
方法,我们使用其全限定名。 完全限定名称是对象引用,该引用以定义对象的名称空间的名称为前缀。
Program.cs
using System;
public class Program
{
static void Main()
{
Console.WriteLine("Simple namespace example");
}
}
通过using
语句,我们将System
命名空间包含到程序中。 现在我们可以调用Console.WriteLine()
而不显式指定System
。
C# 共享名称空间
在下面的代码中,我们有两个共享相同名称空间的文件。
Counter.cs
using System;
namespace Sharing
{
class Counter
{
public int x = 0;
public void Inc()
{
x += 100;
Console.WriteLine(x);
}
}
}
我们有一个Sharing
命名空间。 在命名空间中,我们有一个Counter
类。
namespace Sharing
{
...
}
我们声明一个名为Sharing
的命名空间。 该代码位于Sharing
名称空间的大括号内。
Program.cs
namespace Sharing
{
public class Program
{
static void Main()
{
var counter = new Counter();
counter.Inc();
counter.Inc();
}
}
}
在Program
类中,我们使用上一个文件中的Counter
类。 我们调用其Inc()
方法。
namespace Sharing
{
...
我们在同一个命名空间中工作。
var counter = new Counter();
counter.Inc();
counter.Inc();
我们创建Counter
类的实例。 我们两次调用它的Inc()
方法。 因为我们使用相同名称空间的对象,所以不需要显式指定其名称。
$ dotnet run
100
200
这是示例输出。
C# 不同的名称空间
以下代码示例具有两个不同的名称空间。 我们使用using
关键字从其他名称空间导入元素。
Basic.cs
namespace Mathematical
{
public class Basic
{
public static double PI = 3.141592653589;
public static double GetPi()
{
return PI;
}
}
}
在Basic
类中,我们定义了PI
常量和GetPi()
方法。 Basic
类在Mathematical
命名空间中定义。
Program.cs
using System;
using Mathematical;
namespace Distinct
{
public class Program
{
static void Main()
{
Console.WriteLine(Basic.PI);
Console.WriteLine(Basic.GetPi());
Console.WriteLine(Mathematical.Basic.PI);
Console.WriteLine(Mathematical.Basic.PI);
}
}
}
在此文件中,我们使用MyMath
命名空间中的元素。
using Mathematical;
我们将元素从MyMath
命名空间导入到我们的命名空间中。
Console.WriteLine(Basic.PI)
Console.WriteLine(Basic.GetPI())
现在我们可以使用这些元素。 在我们的例子中,它是Basic
类。
Console.WriteLine(Mathematical.Basic.PI);
Console.WriteLine(Mathematical.Basic.PI);
访问元素的另一种方法是指定元素的全限定名称。
$ dotnet run
3.141592653589
3.141592653589
3.141592653589
3.141592653589
这是输出。
C# 根名称空间
根名称空间是 .NET Framework 库的主空间。 有人可能会创建与 .NET Framework 中的类型或名称空间冲突的类型或名称空间。 在这种情况下,我们可以使用global::
前缀引用根名称空间。
Program.cs
namespace ZetCode
{
class System
{
public override string ToString()
{
return "This is System class";
}
}
public class Program
{
static void Main()
{
var sys = new System();
global::System.Console.WriteLine(sys);
}
}
}
在ZetCode
命名空间中,我们创建一个System
类,该类与.NET Framework 中的类冲突。
var sys = new System();
在这里,我们从ZetCode
命名空间引用System
类。
global::System.Console.WriteLine(sys);
使用global::
前缀,我们指向根名称空间的System
类。
C# 默认名称空间
根名称空间也是 C# 程序的默认名称空间。 命名空间中未包含的元素将添加到未命名的默认命名空间中。
Program.cs
using System;
struct Book
{
public override string ToString()
{
return "Book struct in a default namespace";
}
}
namespace MainProgram
{
struct Book
{
public override string ToString()
{
return "Book struct in a MainProgram namespace";
}
}
public class Program
{
static void Main()
{
Book book1;
global::Book book2;
Console.WriteLine(book1);
Console.WriteLine(book2);
}
}
}
我们有两个Book
结构; 一个在MainProgram
名称空间中定义,另一个在此名称空间之外定义。
struct Book
{
public override string ToString()
{
return "Book struct in a default namespace";
}
}
此Book
结构在名为MainProgram
的自定义命名空间之外定义。 它属于默认名称空间。
Book book1;
我们指的是MainProgram
名称空间内定义的结构。
global::Book book2;
使用global::
前缀,我们指向MainProgram
命名空间之外定义的结构。
$ dotnet run
Book struct in a MainProgram namespace
Book struct in a default namespace
这是程序的输出。
C# 名称空间别名
using
关键字可用于为名称空间创建别名。 使用嵌套的名称空间,全限定名称可能会变长。 我们可以通过创建别名来缩短它们。
Program.cs
namespace ZetCode
{
namespace Items
{
class Book
{
public override string ToString()
{
return "This is a book";
}
}
}
}
namespace MainProgram
{
using ZIB = ZetCode.Items.Book;
public class Aliases
{
static void Main()
{
ZetCode.Items.Book book = new ZetCode.Items.Book();
ZIB book2 = new ZIB();
System.Console.WriteLine(book);
System.Console.WriteLine(book2);
}
}
}
在示例中,我们为Book
类创建了一个别名,该别名被两个名称空间包围。
namespace ZetCode
{
namespace Items
{
class Book
{
...
}
}
}
可以将一个名称空间嵌套到另一个名称空间中。 Book
类的完全限定名称为ZetCode.Items.Book
。
using ZIB = ZetCode.Items.Book;
using
关键字为全限定名ZetCode.Items.Book
创建为ZIB
别名。
ZetCode.Items.Book book = new ZetCode.Items.Book();
ZIB book2 = new ZIB();
我们使用这两个名称来创建书籍实例。
C# 教程的这一部分专门用于名称空间。
C# 集合
在本章中,我们将介绍 C# 集合。 .NET 框架为数据存储和检索提供了专门的类。 在前面的章节中,我们描述了数组。 集合是对数组的增强。
C# 中有三种不同的集合类型:
- 标准
- 泛型
- 并发
标准集合可在System.Collections
下找到。 它们不将元素存储为特定类型的对象,而是存储为Object
类型的对象。 标准集合包括ArrayList
,Hashtable
,Queue
和Stack
。
The generic collections are found under System.Collections.Generic
. Generic collections are more flexible and are the preferred way to work with data. Generics enhance code reuse, type safety, and performance. The generic collections include Dictionary<T, T>
, List<T>
, Queue<T>
, SortedList<T>
, and Stack<T>
.
并发集合包括BlockingCollection<T>
,ConcurrentDictionary<T, T>
,ConcurrentQueue<T>
和ConcurrentStack<T>
。
泛型编程是一种计算机编程样式,其中,算法根据待指定的后来的类型编写,然后在需要作为参数提供的特定类型时实例化。 这种方法由 Ada 于 1983 年率先提出,它允许编写仅在使用时所使用的类型集不同的常见功能或类型,从而减少了重复。 (维基百科)
C# ArrayList
ArrayList
是标准System.Collections
命名空间的集合。 它是一个动态数组。 它提供对元素的随机访问。 添加数据后,ArrayList
会自动扩展。 与数组不同,ArrayList
可以保存多种数据类型的数据。 ArrayList
中的元素通过整数索引访问。 索引从零开始。 ArrayList
末尾的元素索引以及插入和删除操作需要花费固定的时间。 在动态数组的中间插入或删除元素的成本更高。 这需要线性时间。
Program.cs
using System;
using System.Collections;
namespace ArrayListEx
{
class Empty { }
class Program
{
static void Main(string[] args)
{
var data = new ArrayList();
data.Add("Visual Basic");
data.Add(344);
data.Add(55);
data.Add(new Empty());
data.Remove(55);
foreach (object el in data)
{
Console.WriteLine(el);
}
}
}
}
在上面的示例中,我们创建了一个ArrayList
集合。 我们添加了一些元素。 它们具有各种数据类型,字符串,整数和类对象。
using System.Collections;
为了使用ArrayList
集合,我们需要使用System.Collections
命名空间。
var data = new ArrayList();
创建一个ArrayList
集合。
data.Add("Visual Basic");
data.Add(344);
data.Add(55);
data.Add(new Empty());
data.Remove(55);
我们使用Add()
方法向数组添加四个元素。
data.Remove(55);
我们使用Remove()
方法删除一个元素。
foreach(object el in data)
{
Console.WriteLine(el);
}
我们遍历数组并将其元素打印到控制台。
$ dotnet run
Visual Basic
344
ArrayListEx.Empty
这是示例的输出。
C# List
List
是可以通过索引访问的对象的强类型列表。 可以在System.Collections.Generic
命名空间下找到。
Program.cs
using System;
using System.Collections.Generic;
namespace ListEx
{
class Program
{
static void Main(string[] args)
{
var langs = new List<string>();
langs.Add("Java");
langs.Add("C#");
langs.Add("C");
langs.Add("C++");
langs.Add("Ruby");
langs.Add("Javascript");
Console.WriteLine(langs.Contains("C#"));
Console.WriteLine(langs[1]);
Console.WriteLine(langs[2]);
langs.Remove("C#");
langs.Remove("C");
Console.WriteLine(langs.Contains("C#"));
langs.Insert(4, "Haskell");
langs.Sort();
foreach (string lang in langs)
{
Console.WriteLine(lang);
}
}
}
}
在前面的示例中,我们使用List
集合。
using System.Collections.Generic;
List
集合位于System.Collections.Generic
命名空间中。
var langs = new List<string>();
将创建一个通用动态数组。 我们指定将使用在<>
字符内指定类型的字符串。
langs.Add("Java");
langs.Add("C#");
langs.Add("C");
...
我们使用Add()
方法将元素添加到列表中。
Console.WriteLine(langs.Contains("C#"));
我们使用Contains()
方法检查列表是否包含特定的字符串。
Console.WriteLine(langs[1]);
Console.WriteLine(langs[2]);
我们使用索引符号访问List
的第二个和第三个元素。
langs.Remove("C#");
langs.Remove("C");
我们从列表中删除两个字符串。
langs.Insert(4, "Haskell");
我们在特定位置插入一个字符串。
langs.Sort();
我们使用Sort()
方法对元素进行排序。
$ dotnet run
True
C#
C
False
C++
Haskell
Java
Javascript
Ruby
这是示例的结果。
C# 集合初始值设定项
集合初始化器允许在对象创建期间为{}
括号指定集合的元素。
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace CollectionInitializer
{
class Program
{
static void Main(string[] args)
{
var vals = new List<int>() { 1, 2, 3, 4, 5, 6, 7 };
int sum = vals.Sum();
Console.WriteLine(sum);
}
}
}
该示例创建一个列表并打印其总和。 列表的元素在集合初始化器中指定。
$ dotnet run
28
这是输出。
C# SortedList
SortedList<T, T>
表示已排序的键/值对的集合。
Program.cs
using System;
using System.Collections.Generic;
namespace SortedListEx
{
class Program
{
static void Main(string[] args)
{
var sorted = new SortedList<string, int>();
sorted.Add("coins", 3);
sorted.Add("books", 41);
sorted.Add("spoons", 5);
if (sorted.ContainsKey("books"))
{
Console.WriteLine("There are books in the list");
}
foreach (var pair in sorted)
{
Console.WriteLine(pair);
}
}
}
}
该示例使用排序列表来组织项目。
var sorted = new SortedList<string, int>();
排序的列表具有字符串键和整数值。
if (sorted.ContainsKey("books"))
{
Console.WriteLine("There are books in the list");
}
使用ContainsKey()
,我们检查集合中是否有书籍。
foreach (var pair in sorted)
{
Console.WriteLine(pair);
}
使用foreach
循环,我们遍历集合并打印其对。
$ dotnet run
There are books in the list
[books, 41]
[coins, 3]
[spoons, 5]
This is the output.
C# LinkedList
LinkedList
是 C# 中的通用双链表。 LinkedList
仅允许顺序访问。 LinkedList
允许进行固定时间的插入或移除,但只能顺序访问元素。 由于链表需要额外的存储空间以供参考,因此对于诸如字符之类的小型数据项列表来说,它们是不切实际的。 与动态数组不同,可以将任意数量的项目添加到链表(当然受内存限制)而无需重新分配,这是一项昂贵的操作。
Program.cs
using System;
using System.Collections.Generic;
namespace LinkedListEx
{
class Program
{
static void Main(string[] args)
{
var nums = new LinkedList<int>();
nums.AddLast(23);
nums.AddLast(34);
nums.AddLast(33);
nums.AddLast(11);
nums.AddLast(6);
nums.AddFirst(9);
nums.AddFirst(7);
LinkedListNode<int> node = nums.Find(6);
nums.AddBefore(node, 5);
foreach (int num in nums)
{
Console.WriteLine(num);
}
}
}
}
这是一个LinkedList
示例,其中包含一些方法。
var nums = new LinkedList<int>();
这是一个整数LinkedList
。
nums.AddLast(23);
...
nums.AddFirst(7);
我们使用AddLast()
和AddFirst()
方法填充链表。
LinkedListNode<int> node = nums.Find(6);
nums.AddBefore(node, 5);
LinkedList
由节点组成。 我们找到一个特定的节点,并在其之前添加一个元素。
foreach(int num in nums)
{
Console.WriteLine(num);
}
我们正在将所有元素打印到控制台。
$ dotnet run
7
9
23
34
33
11
5
6
This is the output.
C# dictionary
dictionary
,也称为关联数组,是唯一键和值的集合,其中每个键与一个值相关联。 检索和添加值非常快。 字典占用更多内存,因为每个值都有一个键。
Program.cs
using System;
using System.Collections.Generic;
namespace DictionaryEx
{
class Program
{
static void Main(string[] args)
{
var domains = new Dictionary<string, string>();
domains.Add("de", "Germany");
domains.Add("sk", "Slovakia");
domains.Add("us", "United States");
domains.Add("ru", "Russia");
domains.Add("hu", "Hungary");
domains.Add("pl", "Poland");
Console.WriteLine(domains["sk"]);
Console.WriteLine(domains["de"]);
Console.WriteLine("Dictionary has {0} items",
domains.Count);
Console.WriteLine("Keys of the dictionary:");
var keys = new List<string>(domains.Keys);
foreach (string key in keys)
{
Console.WriteLine("{0}", key);
}
Console.WriteLine("Values of the dictionary:");
var vals = new List<string>(domains.Values);
foreach (string val in vals)
{
Console.WriteLine("{0}", val);
}
Console.WriteLine("Keys and values of the dictionary:");
foreach (KeyValuePair<string, string> kvp in domains)
{
Console.WriteLine("Key = {0}, Value = {1}",
kvp.Key, kvp.Value);
}
}
}
}
我们有一本字典,用于将域名映射到其国家名称。
var domains = new Dictionary<string, string>();
我们创建一个包含字符串键和值的字典。
domains.Add("de", "Germany");
domains.Add("sk", "Slovakia");
domains.Add("us", "United States");
...
我们将一些数据添加到字典中。 第一个字符串是键。 第二是值。
Console.WriteLine(domains["sk"]);
Console.WriteLine(domains["de"]);
在这里,我们通过它们的键检索两个值。
Console.WriteLine("Dictionary has {0} items",
domains.Count);
我们通过引用Count
属性来打印项目数。
var keys = new List<string>(domains.Keys);
foreach(string key in keys)
{
Console.WriteLine("{0}", key);
}
这些行从字典中检索所有键。
var vals = new List<string>(domains.Values);
foreach(string val in vals)
{
Console.WriteLine("{0}", val);
}
这些行从字典中检索所有值。
foreach(KeyValuePair<string, string> kvp in domains)
{
Console.WriteLine("Key = {0}, Value = {1}",
kvp.Key, kvp.Value);
}
最后,我们同时打印字典的键和值。
$ dotnet run
Slovakia
Germany
Dictionary has 6 items
Keys of the dictionary:
de
sk
us
ru
hu
pl
Values of the dictionary:
Germany
Slovakia
United States
Russia
Hungary
Poland
Keys and values of the dictionary:
Key = de, Value = Germany
Key = sk, Value = Slovakia
Key = us, Value = United States
Key = ru, Value = Russia
Key = hu, Value = Hungary
Key = pl, Value = Poland
这是示例的输出。
C# queue
queue
是先进先出(FIFO)数据结构。 添加到队列中的第一个元素将是第一个要删除的元素。 队列可以用于处理消息出现时的消息,也可以用于消息到达时为客户服务的消息。 首先服务的是第一个客户。
Program.cs
using System;
using System.Collections.Generic;
namespace QueueEx
{
class Program
{
static void Main(string[] args)
{
var msgs = new Queue<string>();
msgs.Enqueue("Message 1");
msgs.Enqueue("Message 2");
msgs.Enqueue("Message 3");
msgs.Enqueue("Message 4");
msgs.Enqueue("Message 5");
Console.WriteLine(msgs.Dequeue());
Console.WriteLine(msgs.Peek());
Console.WriteLine(msgs.Peek());
Console.WriteLine();
foreach (string msg in msgs)
{
Console.WriteLine(msg);
}
}
}
}
在我们的示例中,我们有一个包含消息的队列。
var msgs = new Queue<string>();
创建字符串队列。
msgs.Enqueue("Message 1");
msgs.Enqueue("Message 2");
...
Enqueue()
将消息添加到队列末尾。
Console.WriteLine(msgs.Dequeue());
Dequeue()
方法删除并返回队列开头的项目。
Console.WriteLine(msgs.Peek());
Peek()
方法从队列中返回下一项,但不会将其从集合中删除。
$ dotnet run
Message 1
Message 2
Message 2
Message 2
Message 3
Message 4
Message 5
Dequeue()
方法从集合中删除Message 1
。 Peek()
方法没有。 Message 2
保留在集合中。
C# Stack
栈是后进先出(LIFO)数据结构。 添加到队列中的最后一个元素将是第一个要删除的元素。 C 语言使用栈将本地数据存储在函数中。 实现计算器时,还将使用该栈。
Program.cs
using System;
namespace StackEx
{
class Program
{
static void Main(string[] args)
{
var myStack = new Stack<int>();
myStack.Push(1);
myStack.Push(4);
myStack.Push(3);
myStack.Push(6);
myStack.Push(4);
Console.WriteLine(myStack.Pop());
Console.WriteLine(myStack.Peek());
Console.WriteLine(myStack.Peek());
Console.WriteLine();
foreach (int item in myStack)
{
Console.WriteLine(item);
}
}
}
}
上面有一个简单的栈示例。
var myStack = new Stack<int>();
创建一个Stack
数据结构。
myStack.Push(1);
myStack.Push(4);
...
Push()
方法将一个项目添加到栈的顶部。
Console.WriteLine(stc.Pop());
Pop()
方法从栈顶部删除并返回该项目。
Console.WriteLine(myStack.Peek());
Peek()
方法从栈顶部返回该项目。 它不会删除它。
$ dotnet run
4
6
6
6
3
4
1
这是程序的输出。
C# 教程的这一部分专门介绍 C# 中的集合。
C# 输入和输出
本章专门介绍 C# 中的输入和输出。 C# 中的输入和输出基于流。
C# 流
流是字节序列的抽象,例如文件,输入/输出设备,进程间通信管道或 TCP/IP 套接字。 流将数据从一个点传输到另一点。 流还能够处理数据。 例如,他们可以压缩或加密数据。 在 .NET Framework 中,System.IO
命名空间包含允许对数据流和文件进行读写的类型。
C# 为File
类中的 I/O 操作提供了高级方法,为StreamReader
或StreamWriter
等类提供了较低级的方法。
处理异常
I/O 操作容易出错。 我们可能会遇到FileNotFoundException
或UnauthorizedAccessException
之类的异常。 与 Java 不同,C# 不会强制程序员手动处理异常。 由程序员决定是否手动处理异常。 如果在try/catch/finally
结构中未手动处理该异常,则该异常由 CLR 处理。
释放资源
I/O 资源必须释放。 可以使用Dispose()
方法在finally
子句中手动释放资源。 using
关键字可用于自动释放资源。 同样,File
类中的方法为我们释放了资源。
示例文本文件
在示例中,我们使用以下简单文本文件:
thermopylae.txt
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.
C# File.ReadAllText
File
提供了用于创建,复制,删除,移动和打开单个文件的静态方法,并有助于创建FileStream
对象。
注意:
File.ReadAllText()
不适合读取非常大的文件。
File.ReadAllText()
打开一个文件,以指定的编码读取文件中的所有文本,然后关闭该文件。
Program.cs
using System;
using System.IO;
using System.Text;
namespace ReadFileIntoString
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
var text = File.ReadAllText(path, Encoding.UTF8);
Console.WriteLine(text);
}
}
}
该程序读取thermopylae.txt
文件的内容,并将其打印到控制台。
var text = File.ReadAllText(path, Encoding.UTF8);
我们一次性将整个文件读成字符串。 在第二个参数中,我们指定编码。
$ dotnet run
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.
这是输出。
C# File.ReadAllLines
File.ReadAllLines()
打开一个文本文件,将文件的所有行读入字符串数组,然后关闭文件。
File.ReadAllLines()
是一种使用 C# 读取文件的便捷方法。 处理非常大的文件时,不应使用它。
Program.cs
using System;
using System.IO;
namespace ReadAllLines
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
string[] lines = File.ReadAllLines(path);
foreach (string line in lines)
{
Console.WriteLine(line);
}
}
}
}
该示例将文件中的所有行读入字符串数组。 我们在foreach
循环中遍历数组,并将每一行打印到控制台。
C# 创建文件
File.CreateText()
创建或打开用于写入 UTF-8 编码文本的文件。 如果文件已经存在,则其内容将被覆盖。
Program.cs
using System;
using System.IO;
namespace CreateFileEx
{
class Program
{
static void Main()
{
var path = @"C:\Users\Jano\Documents\cars.txt";
using (StreamWriter sw = File.CreateText(path))
{
sw.WriteLine("Hummer");
sw.WriteLine("Skoda");
sw.WriteLine("BMW");
sw.WriteLine("Volkswagen");
sw.WriteLine("Volvo");
}
}
}
}
在示例中,我们创建一个cars.txt
文件,并将一些汽车名称写入其中。
using (StreamWriter sw = File.CreateText(path))
CreateText()
方法创建或打开一个文件,用于写入 UTF-8 编码的文本。 它返回一个StreamWriter
对象。
sw.WriteLine("Hummer");
sw.WriteLine("Skoda");
...
我们向流写入两行。
$ cat C:\Users\Jano\Documents\cars.txt
Hummer
Skoda
BMW
Volkswagen
Volvo
我们已成功将五个汽车名称写入文件。
C# 创建,最后写入,最后访问时间
使用File
类,我们可以获取文件的创建,最后写入和最后访问时间。 Exists()
方法确定指定的文件是否存在。
Program.cs
using System;
using System.IO;
namespace FileTimes
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\cars.txt";
if (File.Exists(path))
{
Console.WriteLine(File.GetCreationTime(path));
Console.WriteLine(File.GetLastWriteTime(path));
Console.WriteLine(File.GetLastAccessTime(path));
}
}
}
}
如果存在指定的文件,我们将确定其创建,最后写入和最后访问时间。
if (File.Exists(path))
如果调用方具有所需的权限并且路径包含现有文件的名称,则Exists()
方法返回true
。 否则为false
。 如果path
为null
,无效路径或长度为零的字符串,则此方法还返回false
。
Console.WriteLine(File.GetCreationTime(path));
Console.WriteLine(File.GetLastWriteTime(path));
Console.WriteLine(File.GetLastAccessTime(path));
我们得到指定文件的创建时间,上次写入时间和上次访问时间。
$ dotnet run
10/13/2019 1:59:03 PM
10/13/2019 1:59:03 PM
10/13/2019 1:59:03 PM
这是一个示例输出。
C# 复制文件
File.Copy()
方法将现有文件复制到新文件。 它允许覆盖同名文件。
Program.cs
using System;
using System.IO;
namespace CopyFileEx
{
class Program
{
static void Main(string[] args)
{
var srcPath = @"C:\Users\Jano\Documents\cars.txt";
var destPath = @"C:\Users\Jano\Documents\cars2.txt";
File.Copy(srcPath, destPath, true);
Console.WriteLine("File copied");
}
}
}
然后,我们将文件的内容复制到另一个文件。
var srcPath = @"C:\Users\Jano\Documents\cars.txt";
var destPath = @"C:\Users\Jano\Documents\cars2.txt";
这是源文件和目标文件。
File.Copy(srcPath, destPath, true);
Copy()
方法复制文件。 第三个参数指定是否应覆盖文件(如果存在)。
$ dotnet run
File copied
This is a sample output.
C# IDisposable
接口
流实现IDisposable
接口。 实现此接口的对象必须尽早手动处理。 这是通过在finally
块中调用Dispose()
方法或利用using
语句来完成的。
Program.cs
using System;
using System.IO;
namespace ManualRelease
{
class Program
{
static void Main(string[] args)
{
StreamReader sr = null;
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
try
{
sr = new StreamReader(path);
Console.WriteLine(sr.ReadToEnd());
}
catch (IOException e)
{
Console.WriteLine("Cannot read file");
Console.WriteLine(e.Message);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine("Cannot access file");
Console.WriteLine(e.Message);
}
finally
{
sr?.Dispose();
}
}
}
}
在此示例中,我们从磁盘上的文件读取字符。 我们手动释放分配的资源。
sr = new StreamReader(path);
Console.WriteLine(sr.ReadToEnd());
StreamReader
类用于读取字符。 其父级实现IDisposable
接口。
} catch (IOException e)
{
Console.WriteLine("Cannot read file");
Console.WriteLine(e.Message);
} catch (UnauthorizedAccessException e)
{
Console.WriteLine("Cannot access file");
Console.WriteLine(e.Message);
}
可能的异常在catch
块中处理。
finally
{
sr?.Dispose();
}
在finally
块中,Dispose()
方法清理资源。 使用空条件运算符时,仅当变量不是null
时,才调用该方法。
C# 使用语句
using
语句定义一个范围,在该范围的末尾将放置一个对象。 它提供了一种方便的语法,可确保正确使用IDisposable
对象。
Program.cs
using System;
using System.IO;
namespace AutomaticCleanup
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
using (var sr = new StreamReader(path))
{
Console.WriteLine(sr.ReadToEnd());
}
}
}
}
该示例读取thermopylae.txt
文件的内容。 资源通过using
语句释放。 如果我们不处理 IO 异常,则 CLR 将处理它们。
C# using
声明
using
声明是在using
关键字之后的变量声明。 它告诉编译器声明的变量应放在封闭范围的末尾。 从 C# 8.0 开始,using
声明可用。
Program.cs
using System;
using System.IO;
namespace UsingDeclaration
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
using var sr = new StreamReader(path);
Console.WriteLine(sr.ReadToEnd());
}
}
}
该示例读取thermopylae.txt
文件的内容。 当sr
变量超出范围时(在Main()
方法的末尾),将自动清除资源。
C# MemoryStream
MemoryStream
是用于处理计算机内存中数据的流。
Program.cs
using System;
using System.IO;
namespace MemoryStreamEx
{
class Program
{
static void Main(string[] args)
{
using var ms = new MemoryStream(6);
ms.WriteByte(9);
ms.WriteByte(11);
ms.WriteByte(6);
ms.WriteByte(8);
ms.WriteByte(3);
ms.WriteByte(7);
ms.Position = 0;
int rs = ms.ReadByte();
do
{
Console.WriteLine(rs);
rs = ms.ReadByte();
} while (rs != -1);
}
}
}
我们用MemoryStream
将六个数字写入存储器。 然后,我们读取这些数字并将其打印到控制台。
using var ms = new MemoryStream(6);
该行创建并初始化一个容量为六个字节的MemoryStream
对象。
ms.WriteByte(9);
ms.WriteByte(11);
ms.WriteByte(6);
...
WriteByte()
方法在当前位置的当前流中写入一个字节。
ms.Position = 0;
我们使用Position
属性将光标在流中的位置设置为开头。
do
{
Console.WriteLine(rs);
rs = ms.ReadByte();
} while (rs != -1);
在这里,我们从流中读取所有字节并将其打印到控制台。
$ dotnet run
9
11
6
8
3
7
这是示例的输出。
C# StreamReader
StreamReader
从字节流中读取字符。 默认为 UTF-8 编码。
Program.cs
using System;
using System.IO;
namespace ReadFileEx
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
using var sr = new StreamReader(path);
while (sr.Peek() >= 0)
{
Console.WriteLine(sr.ReadLine());
}
}
}
}
我们读取文件的内容。 这次我们使用ReadLine()
方法逐行读取文件。
while (sr.Peek() >= 0)
{
Console.WriteLine(sr.ReadLine());
}
Peek()
方法返回下一个可用字符,但不使用它。 它指示我们是否可以再次调用ReadLine()
方法。 如果没有要读取的字符,则返回-1
。
C# 计数行
在下一个示例中,我们将对行进行计数。
Program.cs
using System;
using System.IO;
namespace CountingLines
{
class Program
{
static void Main(string[] args)
{
int count = 0;
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
using var sr = new StreamReader(path);
while (sr.ReadLine() != null)
{
count++;
}
Console.WriteLine("There are {0} lines", count);
}
}
}
我们正在计算文件中的行数。
while(stream.ReadLine() != null)
{
count++;
}
在while
循环中,我们使用ReadLine()
方法从流中读取一行。 如果到达输入流的末尾,它将从流或null
中返回一行。
$ dotnet run
There are 3 lines
该文件有三行。
C# StreamWriter
StreamWriter
以特定编码将字符写入流。
Program.cs
using System;
using System.IO;
namespace WriteToFile
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\newfile.txt";
using var sw = new StreamWriter(path);
sw.WriteLine("Today is a beautiful day.");
}
}
}
该示例使用StreamWriter
将字符串写入文件。
using (var sw = new StreamWriter(path))
我们创建一个新的StreamWriter
。 默认值是 UTF-8。 StreamWriter
将路径作为参数。 如果文件存在,它将被覆盖; 否则,将创建一个新文件。
$ dotnet run
$ cat C:\Users\Jano\Documents\newfile.txt
Today is a beautiful day.
我们已经使用cat
命令显示了文件的内容。
C# FileStream
FileStream
为文件提供流,同时支持同步和异步读取和写入操作。
StreamReader
和StreamWriter
处理文本数据,而FileStream
处理字节。
Program.cs
using System.IO;
using System.Text;
namespace FileStreamEx
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\newfile2.txt";
using var fs = new FileStream(path, FileMode.Append);
var text = "Фёдор Михайлович Достоевский\n";
byte[] bytes = new UTF8Encoding().GetBytes(text);
fs.Write(bytes, 0, bytes.Length);
}
}
}
我们用俄语西里尔字母写一些文本到文件中。
using System.Text;
UTF8Encoding
类位于System.Text
命名空间中。
using var fs = new FileStream(path, FileMode.Append);
创建一个FileStream
对象。 第二个参数是打开文件的模式。 附加模式将打开文件(如果存在)并查找到文件末尾,或创建一个新文件。
var text = "Фёдор Михайлович Достоевский";
这是俄文西里尔文的文字。
byte[] bytes = new UTF8Encoding().GetBytes(text);
从俄语西里尔字母文本创建一个字节数组。
fs.Write(bytes, 0, bytes.Length);
我们将字节写入文件流。
$ cat C:\Users\Jano\Documents\newfile2.txt
Фёдор Михайлович Достоевский
我们显示创建文件的内容。
C# XmlTextReader
我们可以使用流来读取 XML 数据。 XmlTextReader
是用于读取 C# 中的 XML 文件的类。 该类是仅转发和只读的。
我们有以下 XML 测试文件:
languages.xml
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language>Python</language>
<language>Ruby</language>
<language>Javascript</language>
<language>C#</language>
</languages>
此文件包含自定义 XML 标记之间的语言名称。
Program.cs
using System;
using System.IO;
using System.Xml;
namespace ReadingXMLFile
{
public class Program
{
static void Main()
{
string path = @"C:\Users\Jano\Documents\languages.xml";
using (var xreader = new XmlTextReader(path))
{
xreader.MoveToContent();
while (xreader.Read())
{
var node = xreader.NodeType switch
{
XmlNodeType.Element => String.Format("{0}: ", xreader.Name),
XmlNodeType.Text => String.Format("{0} \n", xreader.Value),
_ => ""
};
Console.Write(node);
}
}
}
}
}
本示例从 XML 文件读取数据并将其打印到终端。
using System.Xml;
System.Xml
命名空间包含与 Xml 读写相关的类。
using (var xreader = new XmlTextReader(path))
创建一个XmlTextReader
对象。 它是一种读取器,可提供对 XML 数据的快速,非缓存且仅前向访问。 它以文件名作为参数。
xreader.MoveToContent();
MoveToContent()
方法移至 XML 文件的实际内容。
while (xreader.Read())
该行从流中读取下一个节点。 如果没有更多节点,则Read()
方法返回false
。
var node = xreader.NodeType switch
{
XmlNodeType.Element => String.Format("{0}: ", xreader.Name),
XmlNodeType.Text => String.Format("{0} \n", xreader.Value),
_ => ""
};
Console.Write(node);
在这里,我们打印元素名称和元素文本。
$ dotnet run
language: Python
language: Ruby
language: Javascript
language: C#
这是示例的输出。
C# 创建,移动目录
System.IO.Directory
是一个类,具有用于在目录和子目录中创建,移动和枚举的静态方法。
Program.cs
using System;
using System.IO;
namespace DirectoryEx
{
class Program
{
static void Main(string[] args)
{
Directory.CreateDirectory("temp");
Directory.CreateDirectory("newdir");
Directory.Move("temp", "temporary");
}
}
}
我们创建两个目录,然后重命名其中一个目录。 目录在项目文件夹中创建。
Directory.CreateDirectory("temp");
CreateDirectory()
方法创建一个新目录。
Directory.Move("temp", "temporary");
Move()
方法为指定的目录提供一个新名称。
C# DirectoryInfo
DirectoryInfo
公开了用于在目录和子目录中创建,移动和枚举的实例方法。
Program.cs
using System;
using System.IO;
namespace ShowContents
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents";
var dirInfo = new DirectoryInfo(path);
string[] files = Directory.GetFiles(path);
DirectoryInfo[] dirs = dirInfo.GetDirectories();
foreach (DirectoryInfo subDir in dirs)
{
Console.WriteLine(subDir.Name);
}
foreach (string fileName in files)
{
Console.WriteLine(fileName);
}
}
}
}
我们使用DirectoryInfo
类遍历特定目录并打印其内容。
var path = @"C:\Users\Jano\Documents";
var DirInfo = new DirectoryInfo(path);
我们显示指定目录的内容。
string[] files = Directory.GetFiles(path);;
我们使用静态GetFiles()
方法获取目录的所有文件。
DirectoryInfo[] dirs = dir.GetDirectories();
我们得到所有目录。
foreach (DirectoryInfo subDir in dirs)
{
Console.WriteLine(subDir.Name);
}
在这里,我们遍历目录并将其名称打印到控制台。
foreach (string fileName in files)
{
Console.WriteLine(fileName);
}
在这里,我们遍历文件数组并将其名称打印到控制台。
在本章中,我们介绍了 C# 中的输入/输出操作。
C# 目录教程
C# 目录教程显示了如何使用 C# 中的目录。 在我们的示例中,我们创建目录,删除目录,列出目录或获取其权限。
目录定义
目录,也称为文件夹,是在计算机上存储文件的位置。 除文件外,目录还存储其他目录或快捷方式。
在 C# 中,我们可以使用Directory
或DirectoryInfo
处理目录。 Directory
是一个静态类,提供用于处理目录的静态方法。 DirectoryInfo
的实例提供有关特定目录的信息。
这些类在System.IO
命名空间中可用。
C# 创建目录
使用Directory.CreateDirectory()
方法创建目录。
Program.cs
using System;
using System.IO;
namespace CreatingDirectory
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var dirName = $@"{docPath}\test";
DirectoryInfo di = Directory.CreateDirectory(dirName);
Console.WriteLine($"Full name: {di.FullName}, Name: {di.Name}, Parent: {di.Parent}");
if (Directory.Exists(dirName))
{
Console.WriteLine("Directory exists");
}
else
{
Console.WriteLine("Directory does not exist");
}
}
}
}
该示例在用户的Documents
目录中创建一个新的test
目录。
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
我们用Environment.GetFolderPath()
方法确定MyDocuments
目录路径。
var dirName = $@"{docPath}\test";
这是要创建的目录的完整路径。
DirectoryInfo di = Directory.CreateDirectory(dirName);
Console.WriteLine($"Full name: {di.FullName}, Name: {di.Name}, Parent: {di.Parent}");
Directory.CreateDirectory()
创建一个新目录并返回DirectoryInfo
,它代表指定路径下的目录。 从目录信息对象中,我们打印目录的全名,名称和父目录。
if (Directory.Exists(dirName))
{
Console.WriteLine("Directory exists");
}
else
{
Console.WriteLine("Directory does not exist");
}
使用Directory.Exists()
方法,我们可以确定指定的目录是否存在。
C# 获取当前目录
Directory.GetCurrentDirectory()
获取应用的当前工作目录。
Program.cs
using System;
using System.IO;
namespace CurrentDirectory
{
class Program
{
static void Main(string[] args)
{
var curDir = Directory.GetCurrentDirectory();
Console.WriteLine(curDir);
Console.WriteLine(Directory.GetDirectoryRoot(curDir));
}
}
}
该程序将打印当前的工作目录(运行程序的目录)及其根目录。 根由Directory.GetDirectoryRoot()
确定。
$ dotnet run
C:\Users\Jano\Documents\csharp\directory\CurrentDirectory
C:\
这是输出。
C# 删除目录
使用Directory.Delete()
方法删除目录。
Program.cs
using System;
using System.IO;
namespace DeleteDirectory
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var myDir = $@"{docPath}/test3";
Directory.CreateDirectory(myDir);
Console.WriteLine(Directory.Exists(myDir));
Directory.Delete(myDir);
Console.WriteLine(Directory.Exists(myDir));
}
}
}
该示例创建一个新目录,检查它的存在,将其删除,最后再次检查它的存在。
$ dotnet run
True
False
This is the output.
C# 移动目录
Directory.Move()
移动(重命名)目录。
Program.cs
using System;
using System.IO;
namespace MoveDirectory
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var sourceDir = $@"{docPath}\test";
var destDir = $@"{docPath}\test2";
Directory.Move(sourceDir, destDir);
}
}
}
该示例重命名目录。
Directory.Move(sourceDir, destDir);
Directory.Move()
方法的参数是:源目录和目标目录。
C# 列出驱动器
Directory.GetLogicalDrives()
以<drive letter>:\
的形式检索计算机上逻辑驱动器的名称。
Program.cs
using System;
using System.IO;
namespace ListDrives
{
class Program
{
static void Main(string[] args)
{
string[] drives = Directory.GetLogicalDrives();
foreach (string drive in drives)
{
System.Console.WriteLine(drive);
}
}
}
}
该示例列出了计算机上的所有驱动器。
C# 列出目录
Directory.GetDirectories()
返回子目录的名称。 子目录可能符合可选的指定条件。
Program.cs
using System;
using System.IO;
namespace ListDirectories
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string[] myDirs = Directory.GetDirectories(docPath);
Console.WriteLine("Directories:");
foreach (var myDir in myDirs)
{
Console.WriteLine(myDir);
}
}
}
}
该示例列出了指定目录的所有子目录。
在下一个示例中,我们为列出的目录指定一些条件。
Program.cs
using System;
using System.IO;
namespace ListDirectories2
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
Console.WriteLine(docPath);
string[] myDirs = Directory.GetDirectories(docPath, "w*",
SearchOption.TopDirectoryOnly);
Console.WriteLine("Directories:");
foreach (var myDir in myDirs)
{
Console.WriteLine(myDir);
}
}
}
}
该示例列出了所有以w
字符开头的目录。
string[] myDirs = Directory.GetDirectories(docPath, "w*", SearchOption.TopDirectoryOnly);
Directory.GetDirectories()
的第一个参数是要列出的目录。 第二个参数是与要列出的子目录名称匹配的搜索字符串。 第三个参数指定搜索操作应包含所有子目录还是仅包含当前目录。
C# 列出文件
Directory.GetFiles()
返回符合(可选)条件的文件名。
Program.cs
using System;
using System.IO;
namespace ListFiles
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string[] myFiles = Directory.GetFiles(docPath);
Console.WriteLine("Files:");
foreach (var myFile in myFiles)
{
Console.WriteLine(myFile);
}
}
}
}
该示例列出了用户Documents
目录中的所有文件。
C# 目录时间
Directory.GetCreationTime()
获取目录的创建日期和时间。 Directory.GetLastAccessTime()
获取上次访问指定文件或目录的日期和时间。 Directory.GetLastWriteTime()
获取指定文件或目录的最后写入日期和时间。
Program.cs
using System;
using System.IO;
namespace GetTimes
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var myDir = $@"{docPath}\test";
var creationTime = Directory.GetCreationTime(myDir);
var lastAccessTime = Directory.GetLastAccessTime(myDir);
var lastWriteTime = Directory.GetLastWriteTime(myDir);
Console.WriteLine($"Creation time: {creationTime}");
Console.WriteLine($"Last access time: {lastAccessTime}");
Console.WriteLine($"Last write time: {lastWriteTime}");
}
}
}
该示例打印指定目录的创建时间,上次访问时间和上次写入时间。
C# 列出条目
Directory.GetFileSystemEntries()
返回满足指定条件的所有文件和子目录的名称。
Program.cs
using System;
using System.IO;
namespace ListEntries
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string[] entries = Directory.GetFileSystemEntries(docPath, "w*");
Console.WriteLine("Entries:");
foreach (var entry in entries)
{
Console.WriteLine(entry);
}
}
}
}
该程序列出了指定目录中的所有条目。 条目必须以w
字符开头。
C# 目录大小
在下面的示例中,我们确定目录的大小。
Program.cs
using System;
using System.IO;
namespace DirectorySize
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
long size = 0;
var myDir = $@"{docPath}/csharp";
var dirInfo = new DirectoryInfo(myDir);
foreach (FileInfo fi in dirInfo.GetFiles("*", SearchOption.AllDirectories))
{
size += fi.Length;
}
Console.WriteLine($"The directory size: {size} bytes");
}
}
}
要获取目录的大小,我们使用DirectoryInfo
的GetFiles()
方法。 它返回FileInfo
类型的数组。 FileInfo
的Length
属性检索文件的大小。
foreach (FileInfo fi in dirInfo.GetFiles("*", SearchOption.AllDirectories))
{
size += fi.Length;
}
我们搜索指定目录及其子目录中的所有文件。 我们获取每个检索到的文件的大小并添加它们。
C# 复制目录
在以下示例中,我们复制目录。
Program.cs
using System;
using System.IO;
namespace CopyDirectory
{
class Program
{
static void Main(string[] args)
{
var source = @"C:\Users\Jano\Documents\websites";
var dest = @"C:\Users\Jano\Documents\websites-2";
DirectoryCopy(source, dest, true);
Console.WriteLine("Copying finished");
}
private static void DirectoryCopy(string source, string dest, bool copySubDirs = true)
{
var dir = new DirectoryInfo(source);
if (!dir.Exists)
{
throw new DirectoryNotFoundException(
$"Source directory does not exist or could not be found: {source}");
}
DirectoryInfo[] dirs = dir.GetDirectories();
if (!Directory.Exists(dest))
{
Directory.CreateDirectory(dest);
}
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string tempPath = Path.Combine(dest, file.Name);
file.CopyTo(tempPath, false);
}
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string tempPath = Path.Combine(dest, subdir.Name);
DirectoryCopy(subdir.FullName, tempPath, copySubDirs);
}
}
}
}
}
在示例中,我们将目录及其所有子目录复制到新位置。
var source = @"C:\Users\Jano\Documents\websites";
var dest = @"C:\Users\Jano\Documents\websites-2";
我们定义源目录和目标目录。
DirectoryCopy(source, dest, true);
复制委托给DirectoryCopy()
方法。 第三个参数确定是否也复制子目录。
var dir = new DirectoryInfo(source);
if (!dir.Exists)
{
throw new DirectoryNotFoundException(
$"Source directory does not exist or could not be found: {source}");
}
我们从源路径创建一个DirectoryInfo
对象。 如果目录不存在,我们抛出DirectoryNotFoundException
。
DirectoryInfo[] dirs = dir.GetDirectories();
我们使用GetDirectories()
方法获取所有顶级目录。
if (!Directory.Exists(dest))
{
Directory.CreateDirectory(dest);
}
如果目标目录不存在,我们将创建它。
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string tempPath = Path.Combine(dest, file.Name);
file.CopyTo(tempPath, false);
}
我们将文件放在目录中,然后将其复制到新位置。
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string tempPath = Path.Combine(dest, subdir.Name);
DirectoryCopy(subdir.FullName, tempPath, copySubDirs);
}
}
如果设置了copySubDirs
,我们会将子目录及其内容复制到新位置。 我们递归调用DirectoryCopy()
方法。
C# 目录访问控制列表
访问控制列表(ACL)是访问控制条目(ACE)的列表。 ACL 中的每个 ACE 都标识一个受托者,并指定允许,拒绝或审核该受托者的访问权限。
DirectoryInfo
GetAccessControl()
方法获取当前目录的访问控制列表(ACL)条目。
$ dotnet add package System.IO.FileSystem.AccessControl
我们需要添加System.IO.FileSystem.AccessControl
包。
Program.cs
using System;
using System.IO;
using System.Security.AccessControl;
namespace DirectoryACL
{
class Program
{
static void Main(string[] args)
{
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var myDir = $@"{docPath}\test";
var dirInfo = new DirectoryInfo(myDir);
DirectorySecurity dSecurity = dirInfo.GetAccessControl();
AuthorizationRuleCollection acl = dSecurity.GetAccessRules(true, true,
typeof(System.Security.Principal.NTAccount));
foreach (FileSystemAccessRule ace in acl)
{
Console.WriteLine("Account: {0}", ace.IdentityReference.Value);
Console.WriteLine("Type: {0}", ace.AccessControlType);
Console.WriteLine("Rights: {0}", ace.FileSystemRights);
Console.WriteLine("Inherited: {0}", ace.IsInherited);
Console.WriteLine("------------------------");
}
}
}
}
该示例显示指定目录的 ACL。
var dirInfo = new DirectoryInfo(myDir);
创建一个DirectoryInfo
对象。
DirectorySecurity dSecurity = dirInfo.GetAccessControl();
GetAccessControl()
方法返回一个DirectorySecurity
对象,该对象封装了目录的访问控制规则。
AuthorizationRuleCollection acl = dSecurity.GetAccessRules(true, true,
typeof(System.Security.Principal.NTAccount));
我们使用GetAccessRules()
方法获得了一组安全规则。
foreach (FileSystemAccessRule ace in acl)
{
Console.WriteLine("Account: {0}", ace.IdentityReference.Value);
Console.WriteLine("Type: {0}", ace.AccessControlType);
Console.WriteLine("Rights: {0}", ace.FileSystemRights);
Console.WriteLine("Inherited: {0}", ace.IsInherited);
Console.WriteLine("------------------------");
}
我们在一个foreach
循环中枚举访问控制规则。
在本教程中,我们使用了 C# 中的目录。
C# 字典教程
C# 字典教程展示了如何在 C# 中使用Dictionary
集合。
C# 字典
字典,也称为关联数组,是唯一键的集合和值的集合,其中每个键都与一个值关联。 检索和添加值非常快。 字典占用更多内存,因为每个值都有一个键。
C# 字典初始化器
可以使用字面值符号初始化 C# 字典。 这些元素添加在{}
括号内的分配的右侧。
Program.cs
using System;
using System.Collections.Generic;
namespace Initializers
{
class Program
{
static void Main(string[] args)
{
var domains = new Dictionary<string, string>
{
{"sk", "Slovakia"},
{"ru", "Russia"},
{"de", "Germany"},
{"no", "Norway"}
};
Console.WriteLine(domains["sk"]);
var days = new Dictionary<string, string>
{
["mo"] = "Monday",
["tu"] = "Tuesday",
["we"] = "Wednesday",
["th"] = "Thursday",
["fr"] = "Friday",
["sa"] = "Saturday",
["su"] = "Sunday"
};
Console.WriteLine(days["fr"]);
}
}
}
在示例中,我们使用字面值符号创建两个字典。
var domains = new Dictionary<string, string>
{
{"sk", "Slovakia"},
{"ru", "Russia"},
{"de", "Germany"},
{"no", "Norway"}
};
创建一个新的字典。 在尖括号<>
之间,我们指定键和值的数据类型。 新的键/值元素对写在嵌套的{}
括号内; 每对之间用逗号分隔。 例如,"sk"
键引用"Slovakia"
值。
Console.WriteLine(domains["sk"]);
要获得一个值,我们指定字典名称,后跟方括号[]
。 在方括号之间,我们指定键名称。
var days = new Dictionary<string, string>
{
["mo"] = "Monday",
["tu"] = "Tuesday",
["we"] = "Wednesday",
["th"] = "Thursday",
["fr"] = "Friday",
["sa"] = "Saturday",
["su"] = "Sunday"
};
这是替代的 C# 字典初始化器。 使用字典访问符号将值分配给键。
$ dotnet run
Slovakia
Friday
这是输出。
C# 计数元素
通过Count
属性,我们可以获得字典中的键数。
Program.cs
using System;
using System.Collections.Generic;
namespace Counting
{
class Program
{
static void Main(string[] args)
{
var domains = new Dictionary<string, string>
{
{"sk", "Slovakia"},
{"ru", "Russia"},
{"de", "Germany"},
{"no", "Norway"}
};
domains.Add("pl", "Poland");
Console.WriteLine($"There are {domains.Count} items in the dictionary");
}
}
}
该示例计算字典中的项目数。
Console.WriteLine($"There are {domains.Count} items in the dictionary");
在这里,我们打印字典中的项目数。
$ dotnet run
There are 5 items in the dictionary
This is the output.
C# 添加和删除元素
创建字典后,可以在字典中添加或删除新元素。
Program.cs
using System;
using System.Collections.Generic;
namespace AddRemove
{
class Program
{
static void Main(string[] args)
{
var users = new Dictionary<string, int>()
{
{ "John Doe", 41 },
{ "Jane Doe", 38 },
{ "Lucy Brown", 29 },
};
users["Paul Brown"] = 33;
users.Add("Thomas Pattison", 34);
Console.WriteLine(string.Join(", ", users));
users.Remove("Jane Doe");
Console.WriteLine(string.Join(", ", users));
users.Clear();
if (users.Count == 0)
{
Console.WriteLine("The users dictionary is empty");
}
}
}
}
该示例创建一个新词典并使用几种内置方法对其进行修改。
var users = new Dictionary<string, int>()
{
{ "John Doe", 41 },
{ "Jane Doe", 38 },
{ "Lucy Brown", 29 },
};
创建一个新的字典。 用户名是键,用户年龄是值。
users["Paul Brown"] = 33;
users.Add("Thomas Pattison", 34);
我们使用字典访问符号和Add()
方法将两个新对添加到字典中。
Console.WriteLine(string.Join(", ", users));
我们使用字符串Join()
方法一次显示所有元素。
users.Remove("Jane Doe");
使用Remove()
方法删除一对。 该参数是字典键。
users.Clear();
用Clear()
方法清除字典。
$ dotnet run
[John Doe, 41], [Jane Doe, 38], [Lucy Brown, 29], [Paul Brown, 33], [Thomas Pattison, 34]
[John Doe, 41], [Lucy Brown, 29], [Paul Brown, 33], [Thomas Pattison, 34]
The users dictionary is empty
This is the output.
C# ContainsKey
和ContainsValue
方法
ContainsKey()
方法确定字典是否包含指定的键,ContainsValue()
方法确定字典是否包含指定的值。
Program.cs
using System;
using System.Collections.Generic;
namespace CheckElements
{
class Program
{
static void Main(string[] args)
{
var domains = new Dictionary<string, string>
{
{"sk", "Slovakia"},
{"ru", "Russia"},
{"de", "Germany"},
{"no", "Norway"}
};
var key = "sk";
if (domains.ContainsKey(key))
{
Console.WriteLine($"The {key} key is in the dictionary");
} else
{
Console.WriteLine($"The {key} key is in not the dictionary");
}
var value = "Slovakia";
if (domains.ContainsValue(value))
{
Console.WriteLine($"The {value} value is in the dictionary");
} else
{
Console.WriteLine($"The {value} value is in not the dictionary");
}
}
}
}
在示例中,我们检查字典中是否存在"sk"
键和"Slovakia"
值。
$ dotnet run
The sk key is in the dictionary
The Slovakia value is in the dictionary
This is the output.
C# 遍历字典
有几种遍历 C# 字典的方法。
Program.cs
using System;
using System.Collections.Generic;
namespace Traversing
{
class Program
{
static void Main(string[] args)
{
var domains = new Dictionary<string, string> {
{"sk", "Slovakia"},
{"ru", "Russia"},
{"de", "Germany"},
{"no", "Norway"}
};
foreach (var (key, value) in domains)
{
Console.WriteLine($"{key}: {value}");
}
Console.WriteLine("**************************************");
foreach (var kvp in domains)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
Console.WriteLine("**************************************");
// oldschool
foreach (KeyValuePair<string, string> entry in domains)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
}
}
}
该示例使用foreach
遍历字典。
foreach (var (key, value) in domains)
{
Console.WriteLine($"{key}: {value}");
}
在此foreach
循环中,我们成对浏览字典。 每对都分解为其键和值。
foreach (var kvp in domains)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
在这种情况下,我们通过Key
和Value
属性访问键和值。
// oldschool
foreach (KeyValuePair<string, string> entry in domains)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
最后,这是一种使用KeyValuePair
成对遍历字典的较旧方法。
$ dotnet run
sk: Slovakia
ru: Russia
de: Germany
no: Norway
**************************************
sk: Slovakia
ru: Russia
de: Germany
no: Norway
**************************************
sk: Slovakia
ru: Russia
de: Germany
no: Norway
This is the output.
C# 允许分别循环遍历键和值。
Program.cs
using System;
using System.Collections.Generic;
namespace Traversing2
{
class Program
{
static void Main(string[] args)
{
var domains = new Dictionary<string, string>
{
{"sk", "Slovakia"},
{"ru", "Russia"},
{"de", "Germany"},
{"no", "Norway"}
};
Console.WriteLine("Keys:");
foreach (var val in domains.Keys)
{
Console.WriteLine(val);
}
Console.WriteLine("\nValues:");
foreach (var val in domains.Values)
{
Console.WriteLine(val);
}
}
}
}
该示例在两个foreach
循环中打印字典的所有键和所有值。
foreach (var val in domains.Keys)
{
Console.WriteLine(val);
}
我们使用Keys
属性获取所有键。
foreach (var val in domains.Values)
{
Console.WriteLine(val);
}
我们使用Values
属性获取所有值。
$ dotnet run
Keys:
sk
ru
de
no
Values:
Slovakia
Russia
Germany
Norway
This is the output.
C# 排序字典
我们可以使用 LINQ 对字典进行排序。
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sorting
{
class Program
{
static void Main(string[] args)
{
var users = new Dictionary<string, int>()
{
{ "John", 41 },
{ "Jane", 38 },
{ "Lucy", 29 },
{ "Paul", 24 }
};
var sortedUsersByValue = users.OrderBy(user => user.Value);
foreach (var user in sortedUsersByValue)
{
Console.WriteLine($"{user.Key} is {user.Value} years old");
}
}
}
}
该示例按用户年龄对字典进行排序。
var sortedUsersByValue = users.OrderBy(user => user.Value);
OrderBy()
方法用于按条目的值对条目进行排序。
$ dotnet run
Paul is 24 years old
Lucy is 29 years old
Jane is 38 years old
John is 41 years old
输出按字典值排序。
C# SortedDictionary
SortedDictionary
表示按键排序的键/值对的集合。
Program.cs
using System;
using System.Collections.Generic;
namespace SortedDictEx
{
class Program
{
static void Main(string[] args)
{
var sortedUsers = new SortedDictionary<string, int>()
{
{ "John", 41 },
{ "Jane", 38 },
{ "Lucy", 29 },
{ "Paul", 24 }
};
foreach (var user in sortedUsers)
{
Console.WriteLine($"{user.Key} is {user.Value} years old");
}
}
}
}
该示例演示了SortedDictionary
的用法。
$ dotnet run
Jane is 38 years old
John is 41 years old
Lucy is 29 years old
Paul is 24 years old
输出按字典键排序。
在本教程中,我们使用了 C# 词典集合。
您可能也对以下相关教程感兴趣: MySQL C# 教程, C# 中的日期和时间,用 C# 阅读网页或 C# Winforms 教程。
在 C# 中读取文本文件
在本文中,我们展示了如何在 C# 中读取文本文件。 C# 教程是有关 C# 语言的综合教程。
C# 中的输入&输出基于流。 Stream
是所有流的抽象基类。 流是字节序列的抽象,例如文件,输入/输出设备,进程间通信管道或 TCP/IP 套接字。
C# 流
Stream
为输入和输出的类型提供通用接口,并将编程器与操作系统和底层设备的特定详细信息隔离开。 例如,MemoryStream
处理内存中的数据,FileStream
处理文件中的数据。
thermopylae.txt
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.
在我们的示例中,我们将读取以下文件:
C# 使用File.ReadAllText
读取文本文件
File.ReadAllText()
方法打开一个文本文件,将文件的所有行读取为字符串,然后关闭文件。
Program.cs
using System;
using System.IO;
using System.Text;
namespace ReadAllText
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
string content = File.ReadAllText(path, Encoding.UTF8);
Console.WriteLine(content);
}
}
}
该示例读取thermopylae.txt
文件的内容并将其打印到控制台。
C# 使用File.ReadAllLines
读取文本文件
File.ReadAllLines()
打开一个文本文件,将文件的所有行读入字符串数组,然后关闭文件。
Program.cs
using System;
using System.IO;
using System.Text;
namespace ReadAllLines
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
string[] lines = File.ReadAllLines(path, Encoding.UTF8);
foreach (string line in lines)
{
Console.WriteLine(line);
}
}
}
}
使用File.ReadAllLines()
方法读取thermopylae.txt
文件的内容并将其打印到控制台。
foreach (string line in lines)
{
Console.WriteLine(line);
}
我们遍历数组并打印其元素。
C# 使用StreamReader
读取文本文件
StreamReader
设计用于以特定编码输入字符。 它用于从标准文本文件中读取信息行。
使用StreamReader
的ReadToEnd
ReadToEnd()
方法从流的当前位置到其末尾读取所有字符。
Program.cs
using System;
using System.IO;
using System.Text;
namespace StreamReaderReadToEnd
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
using var sr = new StreamReader(fs, Encoding.UTF8);
string content = sr.ReadToEnd();
Console.WriteLine(content);
}
}
}
该示例使用StreamReader
的ReadToEnd()
方法读取文件。
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
FileStream
类为文件提供Stream
,支持同步和异步读取和写入操作。 构造器使用指定的路径,创建模式和读/写权限初始化FileStream
类的新实例。
using var sr = new StreamReader(fs, Encoding.UTF8);
FileStream
被传递到StreamReader
。
string content = sr.ReadToEnd();
StreamReader
的ReadToEnd()
方法读取从当前位置到文件结尾的所有字符。
使用StreamReader
的ReadLine
StreamReader
的ReadLine()
方法从当前流中读取一行字符,并将数据作为字符串返回。
Program.cs
using System;
using System.IO;
using System.Text;
namespace StreamReaderReadLine
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
using var sr = new StreamReader(fs, Encoding.UTF8);
string line = String.Empty;
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
}
该代码示例逐行读取文件。
string line = String.Empty;
while ((line = streamReader.ReadLine()) != null)
{
Console.WriteLine(line);
}
在while
循环中,我们使用StreamReader
的ReadLine()
方法逐行读取文件的内容。
C# 与StreamReader
的ReadToEndAsync
异步读取文本文件
ReadToEndAsync()
方法异步读取从当前位置到流末尾的所有字符,并将它们作为一个字符串返回。
Program.cs
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace ReadTextFileAsync
{
class Program
{
static async Task Main(string[] args)
{
var path = @"C:\Users\Jano\Documents\thermopylae.txt";
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
using var sr = new StreamReader(fs, Encoding.UTF8);
string content = await sr.ReadToEndAsync();
Console.WriteLine(content);
}
}
}
在下一个示例中,我们异步读取文本文件。
static async Task Main(string[] args)
async
修饰符允许在Main()
方法中进行异步操作。
string content = await sr.ReadToEndAsync();
await
运算符应用于异步方法中的任务,以暂停该方法的执行,直到等待的任务完成。
在本文中,我们已经以各种方式在 C# 中阅读了文本文件。
您可能也对以下相关教程感兴趣: MySQL C# 教程, C# 中的日期和时间,用 C# 阅读网页或 C# Winforms 教程。
C# 中的日期和时间
在本教程中,我们展示了如何在 C# 中使用日期和时间。 C# 教程是有关 C# 语言的综合教程。
C# DateTime
DateTime
值类型表示日期和时间,其值的范围是公历 0001 年 1 月 1 日凌晨 00:00:00(午夜)(公元)至 9999 年 12 月 31 日晚上 11:59:59(公元)。
C# TimeSpan
TimeSpan
表示时间间隔(时间或经过的时间),以天,小时,分钟,秒和几分之一秒的正数或负数测量。 TimeZoneInfo
提供了时区信息和可用于不同时区的工具。
C# 今天的日期
在第一个示例中,我们得到今天的日期。
Program.cs
using System;
namespace Today
{
class Program
{
static void Main(string[] args)
{
DateTime now = DateTime.Now;
Console.WriteLine(now.ToString("F"));
}
}
}
该示例打印今天的日期。
DateTime now = DateTime.Now;
通过DateTime
的Now
属性,我们可以获取当地时间的当前日期和时间。
Console.WriteLine(now.ToString("F"));
使用ToString()
方法,我们格式化日期。 F
说明符创建完整的日期和时间模式。
$ dotnet run
Tuesday, November 5, 2019 11:35:23 AM
这是程序的示例输出。
C# DateTime
属性
DateTime
代表时间的瞬间。 它的属性提供日期和时间的各个方面。
Program.cs
using System;
namespace DateTimeProperties
{
class Program
{
static void Main(string[] args)
{
string[] months = {"January", "February", "March", "April", "May",
"June", "July", "September", "October", "November", "December"};
DateTime now = DateTime.Now;
Console.WriteLine("Today's date: {0}", now.Date);
Console.WriteLine("Today is {0} day of {1}", now.Day, months[now.Month - 1]);
Console.WriteLine("Today is {0} day of {1}", now.DayOfYear, now.Year);
Console.WriteLine("Today's time: {0}", now.TimeOfDay);
Console.WriteLine("Hour: {0}", now.Hour);
Console.WriteLine("Minute: {0}", now.Minute);
Console.WriteLine("Second: {0}", now.Second);
Console.WriteLine("Millisecond: {0}", now.Millisecond);
Console.WriteLine("The day of week: {0}", now.DayOfWeek);
Console.WriteLine("Kind: {0}", now.Kind);
}
}
}
该示例检查DateTime
对象的属性。
DateTime now = DateTime.Now;
创建一个DateTime
对象。 DateTime
被设置为该计算机上的当前本地日期和时间。
Console.WriteLine("Today's date: {0}", now.Date);
Date
属性获取DateTime
实例的日期部分。
Console.WriteLine("Today is {0} day of {1}", now.Day, months[now.Month - 1]);
Day
属性获取每月的某天。 Month
属性返回月份部分,表示为 1 到 12 之间的值。
Console.WriteLine("Today is {0} day of {1}", now.DayOfYear, now.Year);
DayOfYear
属性获取年份,Year
属性获取年份。
Console.WriteLine("Today's time: {0}", now.TimeOfDay);
TimeOfDay
属性获取DateTime
实例的一天中的时间。
Console.WriteLine("Hour: {0}", now.Hour);
Console.WriteLine("Minute: {0}", now.Minute);
Console.WriteLine("Second: {0}", now.Second);
Console.WriteLine("Millisecond: {0}", now.Millisecond);
Hour
,Minute
,Second
和Millisecond
是时间分量的一部分。
Console.WriteLine("The day of week: {0}", now.DayOfWeek);
DayOfWeek
属性获取星期几。
Console.WriteLine("Kind: {0}", now.Kind);
Kind
属性返回一个值,该值指示此DateTime
实例表示的时间是基于本地时间,世界协调时间(UTC)还是都不基于。
$ dotnet run
Today's date: 11/5/2019 12:00:00 AM
Today is 5 day of December
Today is 309 day of 2019
Today's time: 11:38:45.6791473
Hour: 11
Minute: 38
Second: 45
Millisecond: 679
The day of week: Tuesday
Kind: Local
This is a sample output of the program.
C# 加减DateTime
DateTime
具有进行时间算术运算的方法。
Program.cs
using System;
namespace TimeArithmetic
{
class Program
{
static void Main(string[] args)
{
DateTime dt = new DateTime(2019, 2, 22, 14, 0, 0);
DateTime dt1 = dt.AddSeconds(55);
DateTime dt2 = dt.AddMinutes(30);
DateTime dt3 = dt.AddHours(72);
DateTime dt4 = dt.AddDays(65);
DateTime dt5 = dt.AddDays(-65);
DateTime dt6 = dt.AddMonths(3);
DateTime dt7 = dt.AddYears(4);
Console.WriteLine(dt1.ToString("F"));
Console.WriteLine(dt2.ToString("F"));
Console.WriteLine(dt3.ToString("F"));
Console.WriteLine(dt4.ToString("F"));
Console.WriteLine(dt5.ToString("F"));
Console.WriteLine(dt6.ToString("F"));
Console.WriteLine(dt7.ToString("F"));
}
}
}
该示例介绍了DateTime
对象的六种方法。
DateTime dt1 = dt.AddSeconds(55);
AddSeconds()
返回一个新的DateTime
,该值将指定的秒数添加到该实例的值。
DateTime dt4 = dt.AddDays(65);
DateTime dt5 = dt.AddDays(-65);
AddDays()
为DateTime
增加了几天。 我们可以提供正值或负值。
$ dotnet run
Friday, February 22, 2019 2:00:55 PM
Friday, February 22, 2019 2:30:00 PM
Monday, February 25, 2019 2:00:00 PM
Sunday, April 28, 2019 2:00:00 PM
Wednesday, December 19, 2018 2:00:00 PM
Wednesday, May 22, 2019 2:00:00 PM
Wednesday, February 22, 2023 2:00:00 PM
这是示例的输出。
C# UTC 时间
我们的星球是一个球体; 它绕其轴旋转。 地球向东旋转,因此太阳在不同位置的不同时间升起。 地球大约每 24 小时旋转一次。 因此,世界被划分为 24 个时区。 在每个时区,都有一个不同的本地时间。 夏令时通常会进一步修改此本地时间。
实际需要一个全球时间。 全球时间可以避免时区和夏令时的混淆。 UTC(世界标准时间)被选为主要时间标准。 UTC 用于航空,天气预报,飞行计划,空中交通管制通关和地图。 与当地时间不同,UTC 不会随季节变化而变化。
Program.cs
using System;
namespace UniversalTime
{
class Program
{
static void Main(string[] args)
{
DateTime now = DateTime.Now;
DateTime utc = DateTime.UtcNow;
Console.WriteLine($"UTC time {utc:HH:mm:ss}");
Console.WriteLine($"Local time {now:HH:mm:ss}");
}
}
}
该示例打印当前的 UTC 时间和本地时间。
DateTime utc = DateTime.UtcNow;
通过DateTime
的UtcNow
属性,我们获得了 UTC 时间。
Console.WriteLine($"UTC time {utc:HH:mm:ss}");
我们格式化时间。
$ dotnet run
UTC time 10:48:16
Local time 11:48:16
对于 CET 时区,时差为一小时。
C# 本地化日期
DateTime
允许我们显示特定区域性的日期和时间。
Program.cs
using System;
using System.Globalization;
namespace Localized
{
class Program
{
static void Main(string[] args)
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
DateTime now = DateTime.Now;
CultureInfo ci = new CultureInfo("sk-SK");
Console.WriteLine($"Dnešný dátum a čas: {now.ToString("F", ci)}");
}
}
}
该示例打印斯洛伐克文化中的当前日期和时间。
Console.OutputEncoding = System.Text.Encoding.UTF8;
为了正确输出带重音的斯洛伐克语字符,我们将控制台输出编码设置为 UTF8。
CultureInfo ci = new CultureInfo("sk-SK");
我们创建了一个斯洛伐克语CultureInfo
,其中包括有关区域性名称,书写系统,使用的日历,字符串的排序顺序以及日期和数字格式的信息。
Console.WriteLine($"Dnešný dátum a čas: {now.ToString("F", ci)}");
我们以完整的日期和时间格式模式打印日期和时间。
$ dotnet run
Dnešný dátum a čas: utorok 5\. novembra 2019 12:03:25
这是程序的输出。
C# Unix 时间
Unix 时间是自 Unix 时代以来的秒数。 Unix 时间广泛用于计算。 没有方法可以在 C# 中获得 Unix 时间。 我们需要创建自己的计算。
Program.cs
using System;
namespace UnixTime
{
class Program
{
static void Main(string[] args)
{
long unixTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
Console.WriteLine(unixTime);
}
}
}
该示例显示 Unix 时间。
long unixTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
我们使用ToUnixTimeSeconds()
方法获得 Unix 时间。
$ dotnet run
1572952370
此刻,自 Unix 时代以来已经过了1572952370
秒。
C# TimeSpan
TimeSpan
结构表示时间间隔。
subtract_times.cs
using System;
namespace SubtractingTimes
{
class Program
{
static void Main(string[] args)
{
string startTime = "7:00 AM";
string endTime = "8:30 PM";
TimeSpan elapsed = DateTime.Parse(endTime).Subtract(DateTime.Parse(startTime));
Console.WriteLine($"Time elapsed: {elapsed}");
}
}
}
在示例中,我们减去两个时间值。
string startTime = "7:00 AM";
string endTime = "8:30 PM";
我们定义了两个表示为字符串的时间值。
TimeSpan elapsed = DateTime.Parse(endTime).Subtract(DateTime.Parse(startTime));
Subtract()
方法用于减去两个时间值。 Parse()
方法将时间间隔的字符串表示形式转换为TimeSpan
对象。
$ dotnet run
Time elapsed: 13:30:00
区别是 13 小时 30 分钟。
在下面的示例中,我们减去两个日期值。
Program.cs
using System;
namespace BorodinoBattle
{
class Program
{
static void Main(string[] args)
{
DateTime now = DateTime.Today;
DateTime borodino_battle = new DateTime(1812, 9, 7);
TimeSpan diff = now - borodino_battle;
Console.WriteLine($"{diff.TotalDays} days have passed since the Battle of Borodino.");
}
}
}
在示例中,我们计算了自 Borodino 战斗以来经过的天数。
DateTime now = DateTime.Today;
DateTime borodino_battle = new DateTime(1812, 9, 7);
我们定义了两个DateTime
对象:一个用于今天,另一个用于在 Borodino 战斗的日期。
TimeSpan diff = now - borodino_battle;
通过减去这两个对象,我们得到一个TimeSpan
对象。
Console.WriteLine($"{diff.TotalDays} days have passed since the Battle of Borodino.");
TotalDays
属性具有经过时间的天数。
$ dotnet run
75664 days have passed since the Battle of Borodino.
自 Borodino 战役以来,于 2019 年 11 月 5 日过去了 75664 天。
C# 格式化时间
日期和时间格式字符串定义了由格式化操作产生的DateTime
或DateTimeOffset
值的文本表示形式。 格式说明符有两种类型:标准和自定义。 自定义日期和时间格式字符串由两个或多个字符组成。
Program.cs
using System;
namespace StandardSpecifiers
{
class Program
{
static void Main(string[] args)
{
DateTime now = DateTime.Now;
Console.WriteLine(now.ToString("d"));
Console.WriteLine(now.ToString("D"));
Console.WriteLine(now.ToString("F"));
Console.WriteLine(now.ToString("M"));
Console.WriteLine(now.ToString("o"));
Console.WriteLine(now.ToString("R"));
Console.WriteLine(now.ToString("t"));
Console.WriteLine(now.ToString("T"));
Console.WriteLine(now.ToString("Y"));
}
}
}
该示例使用某些标准格式说明符打印今天的日期。
Console.WriteLine(now.ToString("d"));
d
说明符创建一个短日期模式。
Console.WriteLine(now.ToString("D"));
D
说明符创建一个长日期模式。
Console.WriteLine(now.ToString("F"));
F
说明符创建完整的日期和时间模式。
Console.WriteLine(now.ToString("M"));
M
说明符创建月和日模式。
Console.WriteLine(now.ToString("o"));
o
说明符创建往返日期和时间模式。 在此模式中,日期和时间部分由T
字符分隔,并且时区偏差附加在字符串的末尾。
Console.WriteLine(now.ToString("R"));
R
说明符创建一个 RFC1123 日期和时间模式。
Console.WriteLine(now.ToString("t"));
t
说明符创建一个短时间模式。
Console.WriteLine(now.ToString("T"));
t
说明符创建长时间模式。
Console.WriteLine(now.ToString("Y"));
Y
说明符创建年和月模式。
$ dotnet run
11/5/2019
Tuesday, November 5, 2019
Tuesday, November 5, 2019 1:05:46 PM
November 5
2019-11-05T13:05:46.5778252+01:00
Tue, 05 Nov 2019 13:05:46 GMT
1:05 PM
1:05:46 PM
November 2019
This is the output of the example.
自定义格式说明符使我们可以创建自定义的日期和时间格式模式。
Program.cs
using System;
namespace CustomSpecifiers
{
class Program
{
static void Main(string[] args)
{
DateTime now = DateTime.Now;
Console.WriteLine(now.ToString("ddd MMM %d, yyyy"));
Console.WriteLine(now.ToString("hh:mm:ss tt"));
}
}
}
该示例使用自定义说明符打印日期和时间格式。
Console.WriteLine(now.ToString("ddd MMM %d, yyyy"));
ddd
指示符是星期几的缩写名称,MMM
是该月的缩写名,d
是该月的某天,从 1 到 31。在自定义说明符的上下文中 ,它前面必须带有%
字符。 最后,yyyy
是四位数的年份。
Console.WriteLine(now.ToString("hh:mm:ss tt"));
hh
说明符是小时,使用从 01 到 12 的 12 小时制时钟,mm
是分钟,从 00 到 59,ss
是秒,从 00 到 59,并且tt
是 AM/PM 指示符。
$ dotnet run
Tue Nov 5, 2019
01:07:51 PM
This is the output of the program.
C# 解析时间
DateTime
的Parse()
方法将日期和时间的字符串表示形式转换为其等效的DateTime
。
Program.cs
using System;
namespace ParseTime
{
class Program
{
static void Main(string[] args)
{
string date_string = "11/5/2019";
DateTime dt = DateTime.Parse(date_string);
Console.WriteLine($"{dt:d MMMM, yyyy}");
}
}
}
程序将日期解析为字符串。
string date_string = "11/5/2019";
这是一个以字符串表示的日期。
DateTime dt = DateTime.Parse(date_string);
使用Parse()
方法,我们将其解析为DateTime
对象。
Console.WriteLine($"{dt:d MMMM, yyyy}");
日期以中端顺序打印到控制台。
$ dotnet run
5 November, 2019
This is the output of the example.
C# 时区
时区是一个使用相同标准时间的区域。 世界上有 24 个时区。
UTC = local time + bias
偏差是 UTC 时间与本地时间之间的时差。
TimeZoneInfo
是用于使用 C# 中的时区的类。
Program.cs
using System;
namespace LocalZone
{
class Program
{
static void Main(string[] args)
{
TimeZoneInfo localZone = TimeZoneInfo.Local;
Console.WriteLine("Current timezone: {0}", localZone.StandardName);
Console.WriteLine("Daylight name: {0}", localZone.DaylightName);
Console.WriteLine("Bias: {0}", localZone.BaseUtcOffset);
}
}
}
该程序将打印当前时区和偏差。
TimeZoneInfo localZone = TimeZoneInfo.Local;
使用Local
属性,我们可以获得本地时区。
Console.WriteLine("Current timezone: {0}", localZone.StandardName);
Console.WriteLine("Daylight name: {0}", localZone.DaylightName);
StandardName
给出时区的标准名称,DaylightName
给出夏令时的名称。
Console.WriteLine("Bias: {0}", localZone.BaseUtcOffset);
BaseUtcOffset
属性产生偏差。
$ dotnet run
Current timezone: Central Europe Standard Time
Daylight name: Central Europe Daylight Time
Bias: 01:00:00
在位于布拉迪斯拉发的系统上,我们获得了这些值。
GetSystemTimeZones()
方法返回所有时区的排序集合,有关这些时区的信息在本地系统上可用。
Program.cs
using System;
namespace TimeZones
{
class Program
{
static void Main(string[] args)
{
var timezones = TimeZoneInfo.GetSystemTimeZones();
foreach (var timezone in timezones)
{
Console.WriteLine(timezone.Id);
}
}
}
}
该示例在系统上打印可用时区的 ID。
$ dotnet run
...
Newfoundland Standard Time
Tocantins Standard Time
E. South America Standard Time
SA Eastern Standard Time
Argentina Standard Time
Greenland Standard Time
Montevideo Standard Time
Magallanes Standard Time
Saint Pierre Standard Time
Bahia Standard Time
UTC-02
Mid-Atlantic Standard Time
Azores Standard Time
Cape Verde Standard Time
UTC
...
这是程序的部分输出。
还可以使用某些格式说明符从DateTime
值中检索时区信息。
Program.cs
using System;
namespace GetTimeZone
{
class Program
{
static void Main(string[] args)
{
DateTime now = DateTime.Now;
Console.WriteLine(now.ToString("%z"));
Console.WriteLine(now.ToString("%K"));
Console.WriteLine(now.ToString("o"));
}
}
}
该示例使用格式说明符来表示本地时区的偏差。
$ dotnet run
+1
+01:00
2019-11-05T13:02:26.2345688+01:00
这是反对。
C# 闰年
闰年是包含另一天的年份。 日历中额外一天的原因是天文日历年与日历年之间的差异。
Program.cs
using System;
namespace LeapYears
{
class Program
{
static void Main(string[] args)
{
// Assume year >= 1582 in the Gregorian calendar.
int[] years = { 2000, 2002, 2004, 2008, 2012, 2016, 2020,
1900, 1800, 1600 };
foreach (int year in years)
{
if (DateTime.IsLeapYear(year))
{
Console.WriteLine($"{year} is a leap year");
}
else
{
Console.WriteLine($"{year} is not a leap year");
}
}
}
}
}
我们有很多年。 我们检查所有年份是否为闰年。 IsLeapYear()
函数确定年份是否为闰年。
int[] years = { 2000, 2002, 2004, 2008, 2012, 2016, 2020,
1900, 1800, 1600 };
这是我们检查的年份。 年份必须在公历中。
foreach (int year in years)
{
if (DateTime.IsLeapYear(year))
{
Console.WriteLine($"{year} is a leap year");
}
else
{
Console.WriteLine($"{year} is not a leap year");
}
}
使用for
循环,我们遍历数组。 我们使用IsLeapYear()
函数检查年份是否为闰年。
$ dotnet run
2000 is a leap year
2002 is not a leap year
2004 is a leap year
2008 is a leap year
2012 is a leap year
2016 is a leap year
2020 is a leap year
1900 is not a leap year
1800 is not a leap year
1600 is a leap year
This is the output of the program.
在本文中,我们使用 C# 处理日期和时间。 在 C# 中读取网页显示了几种在 C# 中抓取网页的方法。 以 C# 读取文本文件涵盖了使用流以 C# 读取文本文件的过程。
在 C# 中读取网页
在本文中,我们展示了如何在 C# 中抓取网页。 C# 教程是有关 C# 语言的综合教程。
本教程显示了如何使用HttpWebRequest
,WebClient
,HttpClient
,Flurl.Http
和RestSharp
读取页面。
在本教程的示例中,我们从一个小型网页 webcode.me 中读取了一个网页。
C# 使用HttpClient
读取网页
HttpClient
提供了一个基类,用于从 URI 标识的资源发送 HTTP 请求和接收 HTTP 响应。
Program.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace DownloadPageHttpClient
{
class Program
{
static async Task Main(string[] args)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("User-Agent", "C# console program");
var content = await client.GetStringAsync("http://webcode.me");
Console.WriteLine(content);
}
}
}
该代码示例使用HttpClient
异步抓取网页。
var content = await client.GetStringAsync("http://webcode.me");
await
运算符将awaitable
作为参数; 检查是否已经完成等待; 如果等待已完成,则该方法继续运行。 GetStringAsync()
将内容读取为字符串,作为异步操作。
$ dotnet run
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My html page</title>
</head>
<body>
<p>
Today is a beautiful day. We go swimming and fishing.
</p>
<p>
Hello there. How are you?
</p>
</body>
</html>
这是输出。
用WebClient
读取网页
WebClient
提供了用于向 URI 标识的资源发送数据和从中接收数据的通用方法。
Program.cs
using System;
using System.Net;
namespace DownloadPageWebClient
{
class Program
{
static void Main(string[] args)
{
using var client = new WebClient();
client.Headers.Add("User-Agent", "C# console program");
string url = "http://webcode.me";
string content = client.DownloadString(url);
Console.WriteLine(content);
}
}
}
该代码示例使用WebClient
获取网页。
string content = client.DownloadString(url);
DownloadString()
方法检索指定的资源。 此方法在下载资源时阻塞。
在第二个示例中,我们为WebClient
提供了一种非阻塞方法。
Program.cs
using System;
using System.Net;
using System.Threading.Tasks;
namespace DownloadPageWebClientAsync
{
class Program
{
static void Main(string[] args)
{
using var client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
Console.WriteLine(e.Result);
};
string url = "http://www.webcode.me";
client.DownloadStringAsync(new Uri(url));
Console.ReadLine();
}
}
}
该代码示例使用WebClient
获取网页的 HTML 代码。 这次操作是异步的。
client.DownloadStringCompleted += (sender, e) =>
{
Console.WriteLine(e.Result);
};
DownloadStringCompleted
事件在异步资源下载操作完成时发生。
client.DownloadStringAsync(new Uri(url));
DownloadStringAsync
方法下载指定为String
或Uri
的资源。 该方法不会阻塞调用线程。
C# 使用HttpWebRequest
读取网页
HttpWebRequest
类提供对属性和方法的支持,这些属性和方法使用户可以使用 HTTP 直接与服务器进行交互。 此 API 现在已标记为过时。
Program.cs
using System;
using System.Net;
using System.IO;
namespace DownloadPageHttpWebRequest
{
class Program
{
static void Main(string[] args)
{
string html = string.Empty;
string url = "http://webcode.me";
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
request.UserAgent = "C# console client";
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
html = reader.ReadToEnd();
}
Console.WriteLine(html);
}
}
}
该示例读取站点的内容并将其打印到控制台中。
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
用WebRequest.Create()
方法创建一个HttpWebRequest
。 它以 URL 作为参数。
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
从请求中,我们使用GetResponse()
方法获得了HttpWebResponse
。
using (Stream stream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(stream))
{
html = reader.ReadToEnd();
}
我们将网页的内容读入字符串。
Console.WriteLine(html);
数据被打印到控制台。
C# 使用Flurl.Http
读取网页
Flurl.Http 是用于 C# 语言的流畅,可移植,可测试的 HTTP 第三方客户端库。
$ dotnet add package Flurl.Http
我们安装Flurl.Http
包。
DownloadPageFlurl.cs
using System;
using System.Threading.Tasks;
using Flurl.Http;
namespace DownloadPageFlurl
{
class Program
{
static async Task Main(string[] args)
{
string result = await "http://webcode.me".GetStringAsync();
Console.WriteLine(result);
}
}
}
该示例读取一个小型网页并将其内容打印到终端。
string result = await "http://webcode.me".GetStringAsync();
await
运算符应用于异步方法中的任务,以暂停该方法的执行,直到等待的任务完成为止。 该任务代表正在进行的工作。 使用GetStringAsync()
扩展方法检索数据。
用RestSharp
读取网页
RestSharp 是.NET 的简单 REST 和 HTTP API 客户端。 它是一个第三方库。
$ dotnet add package RestSharp
我们安装RestSharp
包。
Program.cs
using System;
using RestSharp;
namespace DownloadPageRestSharp
{
class Program
{
static void Main(string[] args)
{
var client = new RestClient("http://webcode.me");
var request = new RestRequest("", Method.GET);
client.ExecuteAsync(request, response =>
{
Console.WriteLine(response.Content);
});
Console.ReadLine();
}
}
}
该代码示例使用 RestSharp 库获取网页的内容。 该网页是异步下载的。
var client = new RestClient("http://www.something.com");
使用RestClient
类创建一个其他客户端。
var request = new RestRequest("", Method.GET);
使用RestRequest
创建 GET 请求。
client.ExecuteAsync(request, response => {
Console.WriteLine(response.Content);
});
该请求使用ExecuteAsync()
方法异步执行。
在本文中,我们展示了如何使用 C# 读取网页。 您可能也对以下相关教程感兴趣: MySQL C# 教程, C# 中的日期和时间,用 C# 读取文本文件或 C# Winforms 教程。
C# HttpClient
教程
C# HttpClient
教程展示了如何使用 C# 中的HttpClient
创建 HTTP 请求。 在示例中,我们创建简单的 GET 和 POST 请求。
超文本传输协议(HTTP)是用于分布式,协作式超媒体信息系统的应用协议。 HTTP 是万维网数据通信的基础。
HttpClient
是用于从 URI 标识的资源发送 HTTP 请求和接收 HTTP 响应的基类。
C# HttpClient
状态码
HTTP 响应状态代码指示特定的 HTTP 请求是否已成功完成。 响应分为五类:
- 信息响应(100–199)
- 成功响应(200–299)
- 重定向(300–399)
- 客户端错误(400–499)
- 服务器错误(500–599)
Program.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HttpClientStatus
{
class Program
{
static async Task Main(string[] args)
{
using var client = new HttpClient();
var result = await client.GetAsync("http://webcode.me");
Console.WriteLine(result.StatusCode);
}
}
}
该示例向小型网站创建 GET 请求。 我们获得了请求的状态码。
using var client = new HttpClient();
创建一个新的HttpClient
。
var result = await client.GetAsync("http://webcode.me");
GetAsync()
方法将 GET 请求作为异步操作发送到指定的 Uri。 await
运算符会暂停对异步方法的求值,直到异步操作完成为止。 异步操作完成后,await
操作符将返回操作结果(如果有)。
$ dotnet run
OK
我们得到 200 OK 状态码; 网站开通了。
C# HttpClient
GET 请求
GET 方法请求指定资源的表示形式。
Program.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HttpClientEx
{
class Program
{
static async Task Main(string[] args)
{
using var client = new HttpClient();
var content = await client.GetStringAsync("http://webcode.me");
Console.WriteLine(content);
}
}
}
该示例向webcode.me
网站发出 GET 请求。 它输出主页的简单 HTML 代码。
var content = await client.GetStringAsync("http://webcode.me");
GetStringAsync()
发送 GET 请求到指定的 Uri,并在异步操作中将响应主体作为字符串返回。
$ dotnet run
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My html page</title>
</head>
<body>
<p>
Today is a beautiful day. We go swimming and fishing.
</p>
<p>
Hello there. How are you?
</p>
</body>
</html>
这是输出。
C# HttpClient
HEAD 请求
如果将使用 HTTP GET 方法请求指定的资源,则 HTTP HEAD 方法请求返回的标头。
Program.cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HttpClientHead
{
class Program
{
static async Task Main(string[] args)
{
var url = "http://webcode.me";
using var client = new HttpClient();
var result = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url));
Console.WriteLine(result);
}
}
}
该示例发出 HEAD 请求。
$ dotnet run
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1,
Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Server: nginx/1.6.2
Date: Sat, 12 Oct 2019 19:55:14 GMT
Connection: keep-alive
ETag: "5d32ffc5-15c"
Accept-Ranges: bytes
Content-Type: text/html
Content-Length: 348
Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT
}
这是响应的标题字段。
C# HttpClient
POST 请求
HTTP POST 方法将数据发送到服务器。 请求正文的类型由Content-Type
标头指示。
$ dotnet add package Newtonsoft.Json
我们需要添加Newtonsoft.Json
包来处理 JSON 数据。
Program.cs
using System;
using System.Text;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace HttpClientPost
{
class Person
{
public string Name { get; set; }
public string Occupation { get; set; }
public override string ToString()
{
return $"{Name}: {Occupation}";
}
}
class Program
{
static async Task Main(string[] args)
{
var person = new Person();
person.Name = "John Doe";
person.Occupation = "gardener";
var json = JsonConvert.SerializeObject(person);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = "https://httpbin.org/post";
using var client = new HttpClient();
var response = await client.PostAsync(url, data);
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
}
}
}
在示例中,我们将 POST 请求发送到https://httpbin.org/post
网站,该网站是面向开发者的在线测试服务。
var person = new Person();
person.Name = "John Doe";
person.Occupation = "gardener";
var json = JsonConvert.SerializeObject(person);
var data = new StringContent(json, Encoding.UTF8, "application/json");
我们借助Newtonsoft.Json
包将对象转换为 JSON 数据。
var response = await client.PostAsync(url, data);
我们使用PostAsync()
方法发送异步 POST 请求。
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
我们读取返回的数据并将其打印到控制台。
$ dotnet run
{
"args": {},
"data": "{\"Name\":\"John Doe\",\"Occupation\":\"gardener\"}",
"files": {},
"form": {},
"headers": {
"Content-Length": "43",
"Content-Type": "application/json; charset=utf-8",
"Host": "httpbin.org"
},
"json": {
"Name": "John Doe",
"Occupation": "gardener"
},
...
"url": "https://httpbin.org/post"
}
This is the output.
C# HttpClient
JSON 请求
JSON(JavaScript 对象表示法)是一种轻量级的数据交换格式。 这种格式对于人类来说很容易读写,对于机器来说,解析和生成都很容易。 它是 XML 的较不冗长且更易读的替代方案。 JSON 的官方互联网媒体类型为application/json
。
Program.cs
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json;
namespace HttpClientJson
{
class Contributor
{
public string Login { get; set; }
public short Contributions { get; set; }
public override string ToString()
{
return $"{Login,20}: {Contributions} contributions";
}
}
class Program
{
private static async Task Main()
{
using var client = new HttpClient();
client.BaseAddress = new Uri("https://api.github.com");
client.DefaultRequestHeaders.Add("User-Agent", "C# console program");
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var url = "repos/symfony/symfony/contributors";
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
var resp = await response.Content.ReadAsStringAsync();
List<Contributor> contributors = JsonConvert.DeserializeObject<List<Contributor>>(resp);
contributors.ForEach(Console.WriteLine);
}
}
}
该示例生成对 Github 的 GET 请求。 它找出了 Symfony 框架的主要贡献者。 它使用Newtonsoft.Json
处理 JSON。
client.DefaultRequestHeaders.Add("User-Agent", "C# console program");
在请求标头中,我们指定用户代理。
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
在accept
标头值中,我们告诉 JSON 是可接受的响应类型。
var url = "repos/symfony/symfony/contributors";
HttpResponseMessage response = await client.GetAsync(url);
var resp = await response.Content.ReadAsStringAsync();
我们生成一个请求并异步读取内容。
List<Contributor> contributors = JsonConvert.DeserializeObject<List<Contributor>>(resp);
contributors.ForEach(Console.WriteLine);
我们使用JsonConvert.DeserializeObject()
方法将 JSON 响应转换为Contributor
对象的列表。
C# HttpClient
下载图像
GetByteArrayAsync()
将 GET 请求发送到指定的 Uri,并在异步操作中将响应主体作为字节数组返回。
Program.cs
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace HttpClientDownloadImage
{
class Program
{
static async Task Main(string[] args)
{
using var httpClient = new HttpClient();
var url = "http://webcode.me/favicon.ico";
byte[] imageBytes = await httpClient.GetByteArrayAsync(url);
string documentsPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
string localFilename = "favicon.ico";
string localPath = Path.Combine(documentsPath, localFilename);
File.WriteAllBytes(localPath, imageBytes);
}
}
}
在示例中,我们从webcode.me
网站下载图像。 图像被写入用户的Documents
文件夹。
byte[] imageBytes = await httpClient.GetByteArrayAsync(url);
GetByteArrayAsync()
将图像作为字节数组返回。
string documentsPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
我们用GetFolderPath()
方法确定Documents
文件夹。
File.WriteAllBytes(localPath, imageBytes);
使用File.WriteAllBytes()
方法将字节写入磁盘。
C# HttpClient
基本认证
在 HTTP 协议中,基本访问认证是 HTTP 用户代理(例如 Web 浏览器或控制台应用)在发出请求时提供用户名和密码的方法。 在基本 HTTP 认证中,请求包含Authorization: Basic <credentials>
形式的标头字段,其中凭据是由单个冒号:
连接的 id 和密码的 base64 编码。
注意:凭证未加密; 因此,必须将 HTTP 基本认证与 HTTPS 协议一起使用。
HTTP 基本认证是用于实现对 Web 资源的访问控制的最简单技术。 它不需要 cookie,会话标识符或登录页面; 相反,HTTP 基本认证使用 HTTP 标头中的标准字段。
Program.cs
using System;
using System.Text;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace HttpClientAuth
{
class Program
{
static async Task Main(string[] args)
{
var userName = "user7";
var passwd = "passwd";
var url = "https://httpbin.org/basic-auth/user7/passwd";
using var client = new HttpClient();
var authToken = Encoding.ASCII.GetBytes($"{userName}:{passwd}");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(authToken));
var result = await client.GetAsync(url);
var content = await result.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
}
该示例将凭据发送到httpbin.org
网站。
var authToken = Encoding.ASCII.GetBytes($"{userName}:{passwd}");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(authToken));
在这里,我们构建认证标头。
var url = "https://httpbin.org/basic-auth/user7/passwd";
该 URL 包含认证详细信息,因为我们在httpbin.org
网站上对其进行了测试。 这样,我们不需要设置自己的服务器。 当然,认证详细信息永远不会放在 URL 中。
$ dotnet run
{
"authenticated": true,
"user": "user7"
}
This is the output.
在本教程中,我们使用 C# HttpClient
创建 HTTP 请求。
您可能也对以下相关教程感兴趣: C# 教程, MySQL C# 教程, C# 中的日期和时间, C# 或 C# Winforms 教程。
ASP.NET Core 教程
ASP.NET Core 教程是 ASP.NET Core 框架的入门教程,该框架用于在 C# 中构建跨平台的 Web 应用。 本教程使用 ASP.NET Core 框架 1.1.0 版。
ASP.NET Core
ASP.NET Core 是一个跨平台,高性能,开放源代码框架,用于构建现代的,基于云的,互联网连接的应用。 ASP.NET Core 是对传统 ASP.NET 框架的重新设计。 您可以在使用 ASP Core 开发 ERP 软件中找到有关 ASP.NET Core 的更多信息。
ASP.NET Core 应用可以在 Windows,Linux 和 Mac 上运行。
ASP.NET Core 功能
以下是 ASP.NET Core 的主要功能:
- 带有 NuGet 包的模块化设计
- 跨平台开发
- 开源和基于社区
- 云端架构
- 内置依赖注入
- 与流行的前端技术集成
安装.NET Core CLI 工具
第一步是为您的操作系统安装.NET Core 命令行工具。 在 Github 存储库上可以找到安装说明。
$ sudo sh -c 'echo "deb [arch=amd64] http://apt-mo.trafficmanager.net/repos/dotnet/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
$ sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
$ sudo apt-get update
我们向 Ubuntu 添加了一个新的非官方存储库。
$ sudo apt-get install dotnet-dev-1.1.0
我们安装dotnet
命令行工具。
.NET Core 控制台应用
我们通过在 C# 中创建一个简单的控制台应用来测试安装。
$ dotnet new console -o consapp
Content generation time: 247.1035 ms
The template "Console Application" created successfully.
$ cd consapp/
$ ls
consapp.csproj Program.cs
使用dotnet
命令,我们创建一个新的控制台应用项目。
consapp.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
</Project>
这是项目配置文件。
Program.cs
using System;
namespace consapp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Console application");
}
}
}
这是一个简单的 C# 程序。
$ dotnet restore
使用dotnet restore
命令,下载必要的依赖项。 它调用 NuGet(.NET 包管理器)以还原依赖关系树。 NuGet 将分析consapp.csproj
文件,下载文件中所述的依赖项(或从计算机上的缓存中获取它们),然后写入obj/project.assets.json
文件。 project.assets.json
文件是编译和运行程序所必需的。
$ dotnet build
Microsoft (R) Build Engine version 15.1.1012.6693
Copyright (C) Microsoft Corporation. All rights reserved.
consapp -> /home/janbodnar/prog/dotnet/consapp/bin/Debug/netcoreapp1.1/consapp.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:06.34
我们使用dotnet build
命令来构建程序。
$ dotnet run
Console application
最后,我们使用dotnet run
运行程序。
Visual Studio 代码
Visual Studio Code 是 Microsoft 为 Windows,Linux 和 MacOS 开发的源代码编辑器。 它包括对调试,嵌入式 Git 控制,语法突出显示,智能代码完成,代码段和代码重构的支持。 它可以用于开发 ASP.NET Core 应用。 可以从网页下载 Visual Studio 代码。
如果选择 Visual Studio Code,则还需要安装 OmniSharp C# 扩展。
ASP.NET Core 应用
在以下应用中,我们创建一个简单的 ASP.NET Core 应用。
$ dotnet new web -o SimpleApp
Content generation time: 221.9237 ms
The template "ASP.NET Core Empty" created successfully.
使用dotnet new web
命令创建一个新的 Web 应用。
$ cd SimpleApp
$ dotnet restore
我们使用dotnet restore
命令恢复包。
Program.cs
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace SimpleApp
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
ASP.NET Core 应用是一个控制台应用,可以通过Main
方法创建 Web 服务器。 我们创建一个 Web 应用主机。 它使用 Kestrel Web 服务器。 Build
和Run
方法构建了IWebHost
对象,该对象将托管应用并开始监听传入的 HTTP 请求。 主机是 Web 服务器的包装器。
WebHostBuilder
上的UseStartup
方法为您的应用指定启动类。 Startup
类必须是公共类,并且必须具有两个方法:ConfigureServices
和Configure
。 ConfigureServices
定义了诸如 MVC 框架或 Entity Framework Core 之类的服务。 Configure
定义了请求管道中的中间件。 中间件是一个处理请求和响应的应用组件。
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace SimpleApp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello there");
});
}
}
}
Startup
类以简单消息响应请求。
loggerFactory.AddConsole();
我们添加一个控制台记录器。
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
在开发模式下,我们使用开发者异常页面。
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello there");
});
我们向请求添加最终处理器。 处理器以文本消息响应。 WriteAsync
创建一个特定的线程来处理请求。 WriteAsync
方法使用 UTF-8 将给定的文本异步写入响应主体流。
$ dotnet run
Hosting environment: Production
Content root path: /home/janbodnar/prog/dotnet/SimpleApp
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
我们使用dotnet run
命令运行该应用。 该应用已启动,将在端口 5000 上监听。
$ curl localhost:5000
Hello there
我们使用curl
工具创建了一个请求,应用以文本消息响应。
ASP.NET Core MVC 应用
在下一个应用中,我们设置一个使用 MVC 模式的 ASP.NET Core 应用。
模型-视图-控制器(MVC)架构模式将应用分为三个区域:模型,视图和控制器。 该模式有助于建立关注点分离。
$ dotnet new web -o WebApp
$ cd WebApp
我们创建一个 ASP.NET Core 应用。
$ mkdir Views Controllers
我们为视图和控制器创建两个目录。
$ dotnet add package Microsoft.AspNetCore.Mvc -v 1.1.3
我们将Microsoft.AspNetCore.Mvc
包添加到项目中。
$ dotnet add package Microsoft.AspNetCore.StaticFiles -v 1.1.2\. This
package adds support for MVC.
我们将Microsoft.AspNetCore.StaticFiles
包添加到项目中。 该包增加了处理静态文件的能力。
WebApp.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.2" />
</ItemGroup>
</Project>
引用将添加到WebApp.csproj
构建文件。
$ dotnet restore
We restore the packages with dotnet restore
command.
Program.cs
using System.IO;
using Microsoft.AspNetCore.Hosting;
namespace WebApp
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
我们设置了 Web 应用主机。 UseContentRoot
方法指定 Web 主机要使用的内容根目录。 内容根是应用使用的任何内容(例如其视图和 Web 内容)的基本路径。
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WebApp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
app.UseDefaultFiles();
app.UseStaticFiles();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "hello",
template: "{controller=Hello}/{action=Index}/");
});
}
}
}
在Startup
中,我们启用 MVC,静态文件并设置路由。
services.AddMvc();
使用AddMvc
,我们将 MVC 中间件添加到应用中。
app.UseDefaultFiles();
app.UseStaticFiles();
我们设置了静态文件。 静态文件将从wwwroot
目录提供。
app.UseMvc(routes =>
{
routes.MapRoute(
name: "hello",
template: "{controller=Hello}/{action=Index}/");
});
使用UseMvc
,我们设置了路由。 路由是将请求 URL 解析为控制器处理器的过程。 控制器名为HelloController
。 操作名称是Index
,这是HelloController
中的方法名称。 该操作的名称也由视图共享,称为Index.cshtml
。 该视图位于Views/Hello
子目录中。 ASP.NET Core 在配置上使用约定,其中可以推断出许多设置,而不必明确声明。
index.html
<!DOCTYPE html>
<html>
<body>
<p>
<a href="Hello?name=Peter&age=23">Get hello message</a>
</p>
</body>
</html>
这是主页。 它包含一个链接,该链接将两个参数发送到 Hello 控制器。 HTML 文件位于wwwroot
目录中。
HelloController.cs
using Microsoft.AspNetCore.Mvc;
using System;
namespace WebApp.Controllers {
public class HelloController : Controller {
public ViewResult Index(string name, int age) {
string msg = String.Format("Hello {0}, you are {1} years old", name, age);
ViewBag.message = msg;
return View();
}
}
}
控制器包含Index
方法,该方法是响应请求而调用的。 它的两个参数自动映射到请求参数(通过配置进行约定)。 该方法生成一个消息字符串,并将其添加到ViewBag
中。 ViewBag
是一个简单的对象,可用于将数据从控制器传输到视图。
当某个操作返回一个视图时,就会发生一个称为视图发现的过程。 视图名称是根据操作的名称推断出来的。
Index.cshtml
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Welcome!</h1>
<div>
@ViewBag.message
</div>
</body>
</html>
Views/Hello
中的Index.cshtml
是Index
操作的视图。 它显示来自ViewBag
的消息。
图:显示消息
在本教程中,我们介绍了 ASP.NET Core 框架。 我们已经创建了两个简单的 ASP.NET Core 应用。
您可能也对以下相关教程感兴趣: C# 教程, C# 中的日期和时间,用 C# Mono 读取网页和 C# Winforms 教程。
Visual Basic 教程
这是 Visual Basic 教程。 在本教程中,您将学习 Visual Basic 语言。 本教程适合初学者。
目录
Visual Basic
Visual Basic 语言是.NET Framework 的高级编程语言。 这是一种非常流行的语言。 目前,它是世界十大流行语言之一。 它是为 Windows 平台创建的。 Mono 项目已为 Linux 和 Mac 平台创建了一个克隆。
相关教程
姐妹语言教程: C# 教程。 关于 ZetCode 的一些教程与 Visual Basic 有关。 Visual Basic GTK# 教程, Visual Basic Qyoto 教程和 Visual Basic Winforms 教程提供了使用 Visual Basic 构建 GUI 的教程。 MySQL Visual Basic 教程和 SQLite Visual Basic 教程提供了使用 Visual Basic 进行数据库编程的教程。
Visual Basic
在 Visual Basic 教程的这一部分中,我们将介绍 Visual Basic 编程语言。
目标
本教程的目的是使您开始使用 Visual Basic 编程语言。 本教程涵盖了 Visual Basic 语言的核心。 变量,数组,控制结构和其他核心功能。 它不涉及图形界面开发。
Visual Basic
Visual Basic 编程语言是.NET Framework 的高级编程语言。 这是一种非常流行的语言。 目前,它是世界十大流行语言之一。 它是为 Windows 平台创建的。 Mono 项目已为 Linux 和 Mac 平台创建了一个克隆。 Visual Basic 的创建易于学习。 它源自 BASIC 语言家族。 它是一种面向对象的编译语言。 源代码被编译为可执行文件(.exe
),由.Net 平台执行。 自最初发布以来,Visual Basic 进行了许多更改。 它因设计不佳而受到严厉批评。 许多缺点已得到解决。 一些不太好的文物仍然保留在语法中。 曾经被认为是 C# 的简单表亲,多年来,它已成为功能齐全的高级编程语言。 它的受欢迎程度上升和下降。
编程语言
当前有几种广泛使用的编程语言。 以下列表基于 TIOBE 编程社区索引。 这些数字来自 2010 年 8 月。请注意,这些数字仅是示例性的。 没有人知道确切的数字。
位置 | 语言 | 份额 |
---|---|---|
1 | Java | 18% |
2 | C | 17.9% |
3 | C++ | 9.7% |
4 | PHP | 9.2% |
5 | Visual Basic | 5.4% |
6 | C# | 5% |
7 | Python | 4.2% |
8 | Perl | 3.4% |
9 | Obj C | 3.2% |
10 | Delphi | 2.4% |
Java 是使用最广泛的编程语言。 Java 在创建便携式移动应用,对各种设备进行编程以及创建企业应用方面表现出色。 每四个应用都使用 C/C++ 进行编程。 它们主要用于创建操作系统和各种桌面应用。 C/C++ 是使用最广泛的系统编程语言。 大多数著名的桌面应用都是用 C++ 创建的。 可能是 MS Office,Macromedia Flash,Adobe Photoshop 或 3D Max。 这两种语言也主导了游戏编程业务。
PHP 在网络上占主导地位。 Java 主要由大型组织使用,而 PHP 由较小的公司和个人使用。
Visual Basic 代表了快速的应用开发的流行和 Microsoft 的统治地位。
C# 计划成为下一个主要语言。 它应该主要与 Java 和 C/C++ 语言竞争。
Python 和 PERL 是流行的脚本语言,并且是紧密的竞争者。
Objective C 的流行源于 Mac 及其创新的设备。
Deplhi 是一种流行的 RAD 开发工具。
编译器
本教程中的示例已在 Linux 和 Windows XP 上进行了测试。 在 Linux 上,我使用了 Mono Visual Basic 编译器。 在 Windows 上,使用 Visual Basic 2008 Express Edition。
在 Linux 上,我们需要安装 Mono Visual Basic 编译器。 它称为vbnc
。
$ vbnc simple.vb
$ ./simple.exe
This is Visual Basic
我们在 Linux 上编译并运行一个简单的 Visual Basic 程序。
在 Windows 下,创建一个新项目。 选择文件/新建项目,或单击 Ctrl + N
,然后选择控制台应用。
图:控制台应用
要运行示例,请单击 Ctrl + F5
。
数据来源
以下三个资源用于创建本教程:
- msdn.com
- wikipedia.org
- Visual Basic 语言规范
在 Visual Basic 教程的这一部分中,我们介绍了 Visual Basic 语言。
Visual Basic 语法结构
像人类语言一样,计算机语言也具有词汇结构。 Visual Basic 程序的源代码由令牌组成。 令牌是原子代码元素。 在 Visual Basic 中,我们具有注释,变量,字面值,运算符,定界符和关键字。
Visual Basic 程序由 Unicode 字符集中的字符组成。
注释
注释被人类用来阐明源代码。 Visual Basic 中的所有注释都使用'
字符或Rem
关键字。
Option Strict On
' This is comments.vb
' Author: Jan Bodnar
' ZetCode 2010
Module Example
Rem program starts here
Sub Main()
Console.WriteLine("This is comments.vb")
End Sub
End Module
Visual Basic 编译器将忽略注释。
空白
Visual Basic 中的空白用于分隔源文件中的标记。 它用于提高源代码的可读性。
在某些地方需要空格。 例如,在Dim
关键字和变量名之间。 在其他地方,这是禁止的。 它不能出现在变量标识符或语言关键字中。
a=1
b = 2
c = 3
标记之间放置的空间量与 Visual Basic 编译器无关。
行继续符
如果一条语句跨越多行,则必须使用行继续符。 这与基于 C 和 C 的语言不同。
Console.WriteLine("The length of the first string is " _
+ str1.Length.ToString() + " characters")
主要是出于可读性原因,我们不想一行中包含太多字符。 我们打破界限,继续下一行。 在 Visual Basic 中,我们必须使用行继续符,否则编译将失败。
变量
变量是一个标识符,它保存一个值。 在编程中,我们说我们为变量分配了一个值。 从技术上讲,变量是对存储值的计算机内存的引用。 变量名称可以包含字母数字字符和下划线。 标识符可以以字符或下划线开头。 它可能不能以数字开头。 变量名称不区分大小写。 这意味着Name
,name
或NAME
引用相同的变量。 变量名称也不能与语言关键字匹配。
Dim name23 As String
Dim _col As Integer
Dim birth_date As Date
这些是有效的 Visual Basic 标识符。
Option Strict On
Module Example
Sub Main()
Dim name As String = "Robert"
Dim Name As String = "Julia"
Console.WriteLine(name)
Console.WriteLine(Name)
End Sub
End Module
由于标识符不区分大小写,因此无法编译此代码。
字面值
字面值是类型的特定值的字面值表示。 字面值类型包括布尔值,整数,浮点数,字符串,字符和日期。 从技术上讲,字面值将在编译时分配一个值,而变量将在运行时分配。
Dim age As Byte = 29
Dim nationality As String = "Hungarian"
在这里,我们为变量分配了两个字面值。 数字 29 和字符串"Hungarian"
是字面值。
Option Strict On
Module Example
Sub Main()
Dim sng As Boolean = True
Dim name As String = "James"
Dim job As String = Nothing
Dim age As Byte = 23
Dim weight As Single = 68.5
Dim born As DateTime = DateValue("November 12, 1987")
Console.WriteLine("His name is {0}", name)
If sng Then
Console.WriteLine("He is single")
Else
Console.WriteLine("He is in a relationship")
End If
Console.WriteLine("His job is {0}", job)
Console.WriteLine("He weighs {0} kilograms", weight)
Console.WriteLine("He was born in {0}", _
Format(born, "yyyy"))
End Sub
End Module
在上面的示例中,我们还有其他字面值。 布尔字面值可以具有True
或False
值。 James
是字符串字面值。 Nothing
代表任何数据类型的默认值。 23
是一个整数字面值。 68.5
是浮点字面值。 最后,November 12, 1987
是日期字面值。
$ ./literals.exe
His name is James
He is single
His job is
He weighs 68.5 kilograms
He was born in 1987
这是程序的输出。
运算符
运算符是用于对某个值执行操作的符号。
+ - * / \ ^ &
= += -= *= /= \= ^=
< > &= >>= <<= >= <=
>> <> <<
这些是 Visual Basic 运算符。 我们将在本教程的后面部分讨论运算符。
分隔符
分隔符是一个或多个字符的序列,用于指定纯文本或其他数据流中单独的独立区域之间的边界。
( ) { } ! # , . : := ?
这些是 Visual Basic 分隔符。
Dim language As String = "Visual Basic"
双精度字符用于标记字符串的开头和结尾。
Console.WriteLine("Today is {0}", GetToday)
括号(方括号)用于标记方法签名。 签名由方法参数组成。 圆括号用于表示求值值。
Dim array() As Integer = { _
2, 4, 5, 6, 7, 3, 2 }
大括号也用于创建数组。
关键词
关键字是 Visual Basic 语言中的保留字。 关键字用于在计算机程序中执行特定任务。 例如,打印值,执行重复性任务或执行逻辑操作。 程序员不能将关键字用作普通变量。
Visual Basic 具有丰富的关键字。 其中许多内容将在本教程中进行解释。 关键字包括If
,Else
,Dim
,For
,Date
,Double
,Or
,Exit
等。
Option Strict On
Module Example
Sub Main()
Dim i As Integer
For i = 0 To 35 Step 5
Console.WriteLine(i)
Next
End Sub
End Module
在上面的示例中,我们使用以下关键字:Option
,On
,Module
,Sub
,Dim
,As
,Integer
,For
,To
,Step
,Next
和End
是 Visual Basic 关键字。
在 Visual Basic 教程的这一部分中,我们介绍了 Visual Basic 语言的基本词汇。