C# 中的关键组织结构概念包括程序、命名空间、类型、成员和程序集。 程序声明类型,而类型则包含成员,并被整理到命名空间中。 类型示例包括类、结构和接口。 成员示例包括字段、方法、属性和事件。 编译完的 C# 程序实际上会打包到程序集中。 程序集的文件扩展名通常为 .exe 或 .dll,具体视其分别实现的是应用程序还是库而定。
C# 程序可以存储在多个源文件中。 在编译 C# 程序时,将同时处理所有源文件,并且源文件可以自由地相互引用。 从概念上讲,就好像所有源文件在被处理之前都连接到一个大文件。 在 C# 中永远都不需要使用前向声明,因为声明顺序无关紧要(极少数例外情况除外)。 C# 并不限制源文件只能声明一种公共类型,也不要求源文件的文件名必须与其中声明的类型相匹配。
程序构建基块
C# 有两种类型:值类型 和 引用类型。 值类型的变量直接包含它们的数据。 引用类型的变量存储对数据(称为“对 象”)的引用。 对于引用类型,两个变量可以引用同一个对象;对一个变量执行的运算可能会影响另一个变量引用 的对象。 借助值类型,每个变量都有自己的数据副本;因此,对一个变量执行的运算不会影响另一个变量( ref 和 out 参数变量除外)。 标识符为变量名称。 标识符是不包含任何空格的 unicode 字符序列。 如果标识符的前缀为 @ ,则该标识符可以 是 C# 保留字。 在与其他语言交互时,使用保留字作为标识符很有用。 C# 的值类型进一步分为:简单类型、枚举类型、结构类型、可以为 null 的值类型和元组值类型。 C# 引用类型又 细分为类类型、接口类型、数组类型和委托类型。
数据类型主要用于指明变量和常量存储值的类型,C# 语言是一种强类型语言,要求每个变量都必须指定数据类型。
C#认可的基础数据类型并没有内置于C#语言中,而是内置于.net Framework中。 如,在c#中声明一个int类型的数据时,声明的实际上是.net结构System.Int32的一个实例。
值类型
引用类型
变量命名
三元运算:int max = a > b ? a : b;
相当于
if(a>b)
{
max=a;
}
else
{
max=b;
}
任何类型A,只要其取值范围完全包含在类型B的取值范围内,就可以隐式转换为类型B
强转要检查溢出,使用checked和unchecked
字符串转换为其它类型
XX.Parse(字符串); 这里的xx代表的如:double,int,bool等
任意类型之间的转换
Convert.ToXX(任何类型);
using System;
namespace OverflowCheck
{
class Program
{
static void Main(string[] args)
{
double number = Convert.ToDouble("Number");
byte destinationVar;
short sourceVar = 281;
destinationVar = checked((byte)sourceVar);
//destinationVar = unchecked((byte)sourceVar);
Console.WriteLine($"sourceVar val: {sourceVar}");
Console.WriteLine($"destinationVar val: {destinationVar}");
}
}
}
装箱是将值类型转换为 object 类型或由此值类型实现的任何接口类型的过程。 常见语言运行时 (CLR) 对值类型进行装箱时,会将值包装在 System.Object 实例中并将其存储在托管堆中。 取消装箱将从对象中提取值类型。 装箱是隐式的;取消装箱是显式的。 装箱和取消装箱的概念是类型系统 C# 统一视图的基础,其中任一类型的值都被视为一个对象。
//将整型变量 i 进行了装箱并分配给对象 o。
int i = 123;
// The following line boxes i.
object o = i;
//可以将对象 o 取消装箱并分配给整型变量 i:
o = 123;
i = (int)o; // unboxing
允许定义一个类型,其取值范围是用户提供的值的有限集合
枚举使用基本类型存储,枚举类型可取的每个值都存储为该基本类型的一个值,默认为int也可以指定为其他基本类型
enum <typeName> : <underlyingType>
{
<value1>=<actualVal1>
}
using System;
namespace Ch05Ex02
{
enum orientation : byte
{
north = 1,
south = 2,
east = 3,
west = 4
}
class Program
{
static void Main(string[] args)
{
byte directionByte;
string directionString;
orientation myDirection = orientation.north;
Console.WriteLine($"myDirection = {myDirection}");
directionByte = (byte)myDirection;
directionString = Convert.ToString(myDirection);
Console.WriteLine($"byte equivalent = {directionByte}");
Console.WriteLine($"string equivalent = {directionString}");
Console.ReadKey();
//orientation myDirection = orientation.north;
//Console.WriteLine($"myDirection = {myDirection}");
//Console.ReadKey();
}
}
}
结构与类相似,它们表示可以包含数据成员和函数成员的数据结构。 但是,与类不同的是,结构是值类型,不需要进行堆分配。 结构类型的变量直接包含结构的数据,而类类型的变量包含对数据的引用,后者称为对象。
结构对包含值语义的小型数据结构特别有用。 复数、坐标系中的点或字典中的键值对都是结构的典型示例。 这些数据结构的关键在于它们有少量的数据成员,它们不需要使用继承或引用标识,并且可以使用赋值语义方便地实现这些数据结构,赋值将复制值而不是引用。
如 简单类型中所述,c # 提供的简单类型(如 int 、 double 和 bool )实际上都是所有结构类型。 正如这些预定义类型是结构一样,还可以使用结构和运算符重载来实现 c # 语言的新 “基元” 类型。
结构是值类型 () 的值 语义 。
所有结构类型都隐式继承自类, System.ValueType (继承) 。
对结构类型的变量赋值会创建 (赋值) 分配的值的副本。
结构的默认值是通过将所有值类型字段设置为其默认值,并将所有引用类型字段设置为 null (默认值) 生成的值。
装箱和取消装箱操作用于在结构类型和 object (装箱和取消装箱) 之间进行转换。
this此访问) (结构的含义不同。
结构的实例字段声明不允许包含变量初始值设定项 (字段初始值设定 项) 。
不允许结构) (构造函数 声明无参数的实例构造函数。
不允许结构将析构函数声明 (析构函数) 。
using System;
namespace Ch05Ex03
{
enum orientation : byte
{
north = 1,
south = 2,
east = 3,
west = 4
}
struct route
{
public orientation direction;
public double distance;
}
class Program
{
static void Main(string[] args)
{
route myRoute;
int myDirection = -1;
double myDistance;
Console.WriteLine("1) North\n2) South\n3) East\n4) West");
do
{
Console.WriteLine("Select a direction:");
myDirection = Convert.ToInt32(Console.ReadLine());
}
while ((myDirection < 1) || (myDirection > 4));
Console.WriteLine("Input a distance:");
myDistance = Convert.ToDouble(Console.ReadLine());
myRoute.direction = (orientation)myDirection;
myRoute.distance = myDistance;
Console.WriteLine($"myRoute specifies a direction of {myRoute.direction} " +
$"and a distance of {myRoute.distance}");
Console.ReadKey();
}
}
}
声明数组:
创建一个数组:int[] myIntArray = new int[5]
using System;
namespace Ch05Ex04
{
class Program
{
static void Main(string[] args)
{
string[] friendNames = { "Todd Anthony", "Mary Chris",
"Autry Rual" };
int i;
Console.WriteLine($"Here are {friendNames.Length} of my friends:");
for (i = 0; i < friendNames.Length; i++)
{
Console.WriteLine(friendNames[i]);
}
Console.ReadKey();
}
}
}
forech循环可以简便定位数组元素
可以使用switch case进行模式匹配
using System;
namespace Ch05Ex05
{
class Program
{
static void Main(string[] args)
{
string[] friendNames = { "Todd Anthony", "Mary Chris",
"Autry Rual", null, "" };
foreach (var friendName in friendNames)
{
switch (friendName)
{
case string t when t.StartsWith("T"):
Console.WriteLine("This friends name starts with a 'T': " +
$"{friendName} and is {t.Length - 1} letters long ");
break;
case string e when e.Length == 0:
Console.WriteLine("There is a string in the array with no value");
break;
case null:
Console.WriteLine("There was a 'null' value in the array");
break;
case var x:
Console.WriteLine("This is the var pattern of type: " +
$"{x.GetType().Name}");
break;
default:
break;
}
}
int sum = 0, total = 0, counter = 0, intValue = 0;
int?[] myIntArray = new int?[7] { 5, intValue, 9, 10, null, 2, 99 };
foreach (var integer in myIntArray)
{
switch (integer)
{
case 0:
Console.WriteLine($"Integer number '{ counter }' has a default value of 0");
counter++;
break;
case int value:
sum += value;
Console.WriteLine($"Integer number '{ counter }' has a value of {value}");
counter++;
break;
case null:
Console.WriteLine($"Integer number '{ counter }' is null");
counter++;
break;
default:
break;
}
}
Console.WriteLine($"The sum of all {counter} is {sum}");
Console.ReadLine();
}
}
}
多维数组
double[,] hillHeight = new double[3,4]
锯齿数组
锯齿数组的大小设置比较灵活,在锯齿数组中,每一行都可以有不同的大小。
迭代锯齿数组中所有元素的代码可以放到嵌套的for循环中。在外层的for循环中,迭代每一行,内层的for循环迭代一行中的每个元素:
int[][] jaggedIntArray
在声明锯齿数组时,要依次放置左右括号。在初始化锯齿数组时,只在第一对方括号中设置该数组包含的行数。定义各行中元素个数的第二个方括号设置为空,因为这类数组的每一行包含不同的元素个数。
int[][] jaggedIntArray = new int[2][];
jaggedIntArray[0] = new int[3];
jaggedIntArray[1] = new int[6];
int[][] jaggedIntArray = { new int[] {1}
new int[] {1,2,3} };
字符串转char数组:
字符串转换大小写:
和
using System;
namespace Ch05Ex06
{
class Program
{
static void Main(string[] args)
{
string myString = "This is a test.";
char[] separator = { ' ' };
string[] myWords;
myWords = myString.Split(separator);
foreach (string word in myWords)
{
Console.WriteLine($"{word}");
}
Console.ReadKey();
}
}
}
可以用
删除字符串空格
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string str=Console.ReadLine();
Console.WriteLine(str);
str = str.ToLower();
str = str.Trim(' ');
Console.WriteLine(str);
Console.ReadKey();
}
}
}
字符串转换为数字
Convert
Convert.ToInt32(str);
Parse:
int.Parse(str)
TryParse
int.TryParse(str)
字符串拼接
StringBuilder stra = new StringBuilder(str1).Append(str2);
string strb = str1 + str2;
string strc = String.Format(str1, str2);
var strd = $"{str1}{str2}";
任何类型的变量都可以声明为“不可为 null”或“可为 null”_。 可为 null 的变量包含一个额外的 null 值,表示没有值。 可为 null 的值类型(结构或枚举)由 System.Nullable
表示。 不可为 null 和可为 null 的引用类型都由基础引用类型表示。 这种区别由编译器和某些库读取的元数据体现。 当可为 null 的引用在没有先对照 null 检查其值的情况下取消引用时,编译器会发出警告。 当对不可为 null 的引用分配了可能为 null 的值时,编译器也会发出警告。
参考
https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/nullable-value-types
https://docs.microsoft.com/zh-cn/dotnet/csharp/nullable-references
略
在C#中函数和方法是有一点点差别的
关键字 函数名(参数)
{
代码块
return 返回值
}
函数的参数是值传递,即函数中的改变不影响参数
using System;
namespace Ch06Ex01
{
class Program
{
static string myString;
static void Write()
{
//Console.WriteLine("Text output from function.");
string myString = "String defined in Write()";
Console.WriteLine("Now in Write()");
//Console.WriteLine($"myString = {myString}");
Console.WriteLine($"Local myString = {myString}");
Console.WriteLine($"Global myString = {Program.myString}");
}
static void Main(string[] args)
{
string myString = "String defined in Main()";
Program.myString = "Global string";
Write();
Console.WriteLine("\nNow in Main()");
//Console.WriteLine($"myString = {myString}");
Console.WriteLine($"Local myString = {myString}");
Console.WriteLine($"Global myString = {Program.myString}");
Console.ReadKey();
}
}
}
使用ref
关键字指定参数进行引用传递
注意:使用ref,首先需要使用”不是常量“的变量,其次是必须使用初始化过的变量,C#不允许ref参数在调用他的函数中初始化
该参数在函数结束后可能会改变值
using System;
namespace ConsoleApp1
{
class Program
{
static void MaxValue(int[] intArray,ref int maxIndex)
{
int maxVal = intArray[0];
maxIndex = 0;
for(int i = 1; i < intArray.Length; i++)
{
if (intArray[i] > maxVal)
{
maxVal = intArray[i];
maxIndex = i;
}
}
}
static void Main(string[] args)
{
int maxIndex = 1;
int[] intArray = { 1, 7, 2, 4, 9 };
MaxValue(intArray, ref maxIndex);
Console.WriteLine($"maxIndex={maxIndex}");
}
}
}
out 关键字通过引用传递参数。 它让形参成为实参的别名,这必须是变量。 换而言之,对形参执行的任何操作都是对实参执行的。 它与 ref 关键字相似,只不过 ref 要求在传递之前初始化变量。 它也类似于 in 关键字,只不过 in 不允许通过调用方法来修改参数值。 若要使用 out 参数,方法定义和调用方法均必须显式使用 out 关键字。
使用out
关键字指定参数为输出参数,在函数执行完毕后,该参数的值将返回给函数调用中使用的变量,out参数额外返回参数
注意:out参数可以使用未赋值的变量,使用时也将它看作未赋值
可以把已经赋值的变量用作out参数,但是存储的值会丢失
结束当前函数时必须对out参数赋值
using System;
namespace ConsoleApp1
{
class Program
{
static void MaxValue(int[] intArray,out int maxIndex)
{
int maxVal = intArray[0];
maxIndex = 0;
for(int i = 1; i < intArray.Length; i++)
{
if (intArray[i] > maxVal)
{
maxVal = intArray[i];
maxIndex = i;
}
}
}
static void Main(string[] args)
{
int maxIndex;
int[] intArray = { 1, 7, 2, 4, 9 };
MaxValue(intArray, out maxIndex);
Console.WriteLine($"maxIndex={maxIndex}");
}
}
}
元组用于将多个数据元素分组成一个轻型数据结构,可以返回多个参数
若要定义元组类型,需要指定其所有数据成员的类型,或者,可以指定字段名称。 虽然不能在元组类型中定义方法,但可以使用 .NET 提供的方法
using System;
using System.Collections.Generic;
using System.Linq;
namespace Tuple
{
class Program
{
static void Main(string[] args)
{
IEnumerable<int> numbers = new int[] { 1, 2, 3, 4, 5, 6 };
var result = GetMaxMin(numbers);
Console.WriteLine($"Max number is {result.max}, " +
$"Min number is {result.min}, " +
$"Average is {result.average}");
Console.ReadLine();
}
private static (int max, int min, double average) GetMaxMin(IEnumerable<int> numbers)
{
return (Enumerable.Max(numbers),
Enumerable.Min(numbers),
Enumerable.Average(numbers));
}
}
}
var xs = new[] { 4, 7, 9 };
var limits = FindMinMax(xs);
Console.WriteLine($"Limits of [{string.Join(" ", xs)}] are {limits.min} and {limits.max}");
// Output:
// Limits of [4 7 9] are 4 and 9
var ys = new[] { -9, 0, 67, 100 };
var (minimum, maximum) = FindMinMax(ys);
Console.WriteLine($"Limits of [{string.Join(" ", ys)}] are {minimum} and {maximum}");
// Output:
// Limits of [-9 0 67 100] are -9 and 100
(int min, int max) FindMinMax(int[] input)
{
if (input is null || input.Length == 0)
{
throw new ArgumentException("Cannot find minimum and maximum of a null or empty array.");
}
var min = int.MaxValue;
var max = int.MinValue;
foreach (var i in input)
{
if (i < min)
{
min = i;
}
if (i > max)
{
max = i;
}
}
return (min, max);
}
static 静态
const 常量
使用static全局变量可能在其他地方被代码改变了值,但是再次使用这个变量时如果没有意识到这个修改会出现问题。
Main()函数是应用程序的入口,执行这个函数就是执行应用程序,这个函数执行完毕时执行过程就结束了
除了数据结构还可以包含函数
struct Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
注意Point函数可以直接访问结构成员
允许创建多个函数名相同但是参数不同的同名函数
因为函数的签名包括函数名和参数,签名不同的函数没有问题,但是函数的签名不包括返回类型,所以不能定义两个仅返回类型不同的函数,它们实际上有相同的签名
委托(delegate)是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,可以将其实例与任何具有兼容签名和返回类型的方法相关联。 可以通过委托实例调用方法。委托用于将方法作为参数传递给其他方法。 事件处理程序就是通过委托调用的方法。 你可以创建一个自定义方法,当发生特定事件时,某个类(如 Windows 控件)就可以调用你的方法。
using System;
namespace Ch06Ex05
{
class Program
{
delegate double ProcessDelegate(double param1, double param2);
static double Multiply(double param1, double param2) => param1 * param2;
static double Divide(double param1, double param2) => param1 / param2;
static void Main(string[] args)
{
ProcessDelegate process;
Console.WriteLine("Enter 2 numbers separated with a comma:");
string input = Console.ReadLine();
int commaPos = input.IndexOf(',');
double param1 = Convert.ToDouble(input.Substring(0, commaPos));
double param2 = Convert.ToDouble(input.Substring(commaPos + 1,
input.Length - commaPos - 1));
Console.WriteLine("Enter M to multiply or D to divide:");
input = Console.ReadLine();
if (input == "M")
process = new ProcessDelegate(Multiply);
else
process = new ProcessDelegate(Divide);
Console.WriteLine($"Result: {process(param1, param2)}");
Console.ReadKey();
}
}
}
可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。 该方法可以是静态方法,也可以是实例方法。 此灵活性意味着你可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。
== 备注:在方法重载的上下文中,方法的签名不包括返回值。 但在委托的上下文中,签名包括返回值。 换句话说,方法和委托必须具有相同的返回类型。==
将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。 可编写一个比较应用程序中两个对象的方法。 该方法可用在排序算法的委托中。 由于比较代码与库分离,因此排序方法可能更常见。
委托具有以下属性:
委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。
委托允许将方法作为参数进行传递。
委托可用于定义回调方法。
委托可以链接在一起;例如,可以对一个事件调用多个方法。
方法不必与委托类型完全匹配。 有关详细信息,请参阅使用委托中的变体。
使用 Lambda 表达式可以更简练地编写内联代码块。 Lambda 表达式(在某些上下文中)可编译为委托类型。
委托是安全封装方法的类型,类似于 C 和 C++ 中的函数指针。 与 C 函数指针不同的是,委托是面向对象的、类型安全的和可靠的。 委托的类型由委托的名称确定。
// A set of classes for handling a bookstore:
namespace Bookstore
{
using System.Collections;
// Describes a book in the book list:
public struct Book
{
public string Title; // Title of the book.
public string Author; // Author of the book.
public decimal Price; // Price of the book.
public bool Paperback; // Is it paperback?
public Book(string title, string author, decimal price, bool paperBack)
{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
}
}
// Declare a delegate type for processing a book:
public delegate void ProcessBookCallback(Book book);
// Maintains a book database.
public class BookDB
{
// List of all books in the database:
ArrayList list = new ArrayList();
// Add a book to the database:
public void AddBook(string title, string author, decimal price, bool paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}
// Call a passed-in delegate on each paperback book to process it:
public void ProcessPaperbackBooks(ProcessBookCallback processBook)
{
foreach (Book b in list)
{
if (b.Paperback)
// Calling the delegate:
processBook(b);
}
}
}
}
// Using the Bookstore classes:
namespace BookTestClient
{
using Bookstore;
// Class to total and average prices of books:
class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m;
internal void AddBookToTotal(Book book)
{
countBooks += 1;
priceBooks += book.Price;
}
internal decimal AveragePrice()
{
return priceBooks / countBooks;
}
}
// Class to test the book database:
class Test
{
// Print the title of the book.
static void PrintTitle(Book b)
{
Console.WriteLine($" {b.Title}");
}
// Execution starts here.
static void Main()
{
BookDB bookDB = new BookDB();
// Initialize the database with some books:
AddBooks(bookDB);
// Print all the titles of paperbacks:
Console.WriteLine("Paperback Book Titles:");
// Create a new delegate object associated with the static
// method Test.PrintTitle:
bookDB.ProcessPaperbackBooks(PrintTitle);
// Get the average price of a paperback by using
// a PriceTotaller object:
PriceTotaller totaller = new PriceTotaller();
// Create a new delegate object associated with the nonstatic
// method AddBookToTotal on the object totaller:
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
totaller.AveragePrice());
}
// Initialize the book database with some test books:
static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("The C Programming Language", "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
bookDB.AddBook("The Unicode Standard 2.0", "The Unicode Consortium", 39.95m, true);
bookDB.AddBook("The MS-DOS Encyclopedia", "Ray Duncan", 129.95m, false);
bookDB.AddBook("Dogbert's Clues for the Clueless", "Scott Adams", 12.00m, true);
}
}
}
/* Output:
Paperback Book Titles:
The C Programming Language
The Unicode Standard 2.0
Dogbert's Clues for the Clueless
Average Paperback Book Price: $23.97
*/