ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

《c#10 in a nutshell》--- 读书随记(2)

2022-07-02 22:00:57  阅读:162  来源: 互联网

标签:10 Console c# int WriteLine 类型 new ref 随记


Chaptor 1 . C# Language Basics

内容来自书籍《C# 10 in a Nutshell》
Author:Joseph Albahari
需要该电子书的小伙伴,可以留下邮箱,有空看到就会发送的

A First C# Program

int x = 12 * 30;
System.Console.WriteLine (x);

计算 12 * 30的结果,然后存储在变量x中,因为C#是强类型的语言,所以所有变量的类型都必须是编译期间已知

然后Console类的静态方法WriteLine,前面的System是命名空间,我们也可以用using System来导入这个命名空间下的所有公开的API,这样就不需要频繁编写前缀了Console.WriteLine (x);

using System;

Console.WriteLine (FeetToInches (100));

int FeetToInches (int feet)
{
    int inches = feet * 12;
    return inches;
}

方法的声明,前面是返回值类型,方法名,入参的形参变量类型和名称。方法体是用一个大括号包裹着。和Java的不一样,它的大括号是都在方法签名的下方,Java的是有一个大括号在方法签名的末尾。如果方法没有返回值,那么应该使用void类型,代表这个方法没有返回值

Compilation

C#的编译器会将源码编译到assembly.,一个assembly是包的集合。它可以是一个应用或者库,它们之间的不同是,应用是有一个入口的执行函数,但是库没有入口。

库的目的是被应用所引用或者被其他库引用。

每个程序的的都有一个被叫做top-level statements的入口文件,这个文件会隐式创建一个入口函数,也就是常说的Main函数

dotnet命令行工具,是可以用来管理.NET源码和二进制程序的,它可以构建程序,运行程序

// 创建一个Console程序
dotnet new Console -n MyFirstProgram

// 构建程序,然后运行这个程序
dotnet run MyFirstProgram

// 构建这个程序
dotnet build MyFirstProgram.csproj

需要注意的是,在执行dotnet命令的时候,最好处于项目内部

Syntax

Identifiers and Keywords

using System;
int x = 12 * 30;
Console.WriteLine (x);

System, x, Console, WriteLine,都是标识符,是开发者用来选择类、方法、变量等

标识符可以使用下划线、字母开头的Unicode字符集,区分大小写,按照约定,参数、变量和私有字段应该用camel case,而其他的所有标识符应该用Pascal case

Keywords关键字,指的是对编译器来说,是有特殊含义的一些单词,这里使用到了using, int,要注意的是,不能使用关键字作为标识符

如果真的希望使用关键字作为标识符,可以添加@前缀给标识符

Comments

单行注释//

多行注释/* ... */

文档注释///

Type Basics

在C#中,所有的值都是类型的实例

Predefined Type Examples

预定义类型是编译器提供的特殊的类型。比如说基本数字类型int,还有字符串类型string,布尔值bool

Custom Types

public class UnitConverter
{
    int ratio;      // Field 字段

    public UnitConverter(int unitRatio)  // Constructor 构造函数
    {
        ratio = unitRatio;
    }

    public int Convert(int unit)    // Method   方法
    {
        return unit * ratio;
    }
}

The public keyword

如果标注了public关键字,会将这个类型的成员暴露出来,如果没有标注访问权限,默认是private的权限

Defining namespaces

namespace A 
{
    public class B
    {
        ...
    }
}

类型B的命名空间在A,在调用B的时候,需要带上new A.B(),才能使用

Types and Conversions

两个合适的类型可以进行转换,可以进行隐式或者显式的自动转换

int x = 12345;
long y = x; // Implicit
short z = (short)x;  // Explicit

Implicit的转换会发生在两种情况:

  • 编译器可以保证这个转换总是成功的
  • 没有信息会在转换中丢失

Explicit的转换只需要其中一种:

  • 编译器不能保证转换是成功
  • 信息可能在转换中丢失

Value Types Versus Reference Types

所有的c# 类型都可以区分为下面的几种类别中:

  • Value types 值类型
  • Reference types 引用类型
  • Pointer types 指针类型
  • Generic type parameters 范型类型参数

值类型一般是内建的基础类型,比如那些数字类型intlong,还有字符类型char等,还有就是我们自定义的struct类型和enum类型也算是值类型

引用类型包括所有的classarraydelegateinterface类型(包括string也是引用类型)

值类型和引用类型的不同之处在,它们处理内存的方式不同

Value types

值类型或者常量的内容,只是一个简单的值,比如int内置类型,它是一个32bit的数据,也可以自定义struct类型

public struct Point 
{ 
    public int X, Y; 
}

它的内存视图:

值类型的实例在赋值的时候,总是copy的行为

Point p1 = new Point();
p1.X = 7;
Point p2 = p1;// Assignment causes copy
p1.X = 9;

Reference types

引用类型要更加复杂,它有两个部分组成一个是对象,一个是指向对象的引用。

它的内存视图

在对引用类型做赋值动作的时候,只会cpoy引用,而不是对象实例。这样就造成,可以同时有多个引用变量指向同一个对象。

Point p1 = new Point();
p1.X = 7;
Point p2 = p1;// Copies p1 reference
p1.X = 9;// Change p1.X

Null

一个引用类型,可以被赋值为null,表明这个引用没有指向任何对象

Storage overhead

值类型占用的内存大小是刚刚好的,比如说结构体Point,它的占用大小是8bytes

struct Point
{
    int x; // 4 bytes
    int y; // 4 bytes
}

需要注意的是,这个大小是会有内存对齐的原因,而导致和你预想的不一样

struct A { byte b; long l; }

这个看起来可能是占用7bytes,但是实际是16bytes,因为会对齐最大的那个字段的大小

Specialized Operations on Integral Types

Overflow check operators

checked操作符可以在运行时生成一个错误OverflowException,这样比变量溢出了,但是没有任何反馈。

int c = checked (a * b);// 只是检查这个表达式

// 检查block内的代码
checked
{
    ...
    c = a * b;
    ...
}

也可以解除这种检查,通过使用关键字unchecked

关于常量的计算溢出,是在编译时做检查,所以不会隐式溢出

8- and 16-Bit Integral Types

8bit和16bit的整数类型,有byte、sbyte、short和ushort,这些类型的计算操作,C#会隐式地转换为更大的类型,而这会造成一个编译时错误

short x = 1, y = 1;
short z = x + y;    // Compile-time error

short z = (short) (x + y);  // OK

Strings and Characters

C#的char类型代表了一个Unicode字符,占用2bytes(UTF-16)

String Type

C#的一个字符串代表一个不可变的Unicode字符序列

在字符串的前面添加@,代表字符串的内容不做转义

String interpolation

插值字符串

int x = 4;
Console.Write ($"A square has {x} sides");

Arrays

char[] vowels = new char[5];    // 声明一个字符数组

char[] vowels = new char[] {'a','e','i','o','u'};   声明并初始化一个字符数组

char[] vowels = {'a','e','i','o','u'};

Default Element Initialization

创建一个数组,总是会预初始化数组元素为默认值。默认值在元素是值类型或者引用类型是有性能影响的。

如果元素是值类型,那么每个元素都会作为元素的一部分初始化

Point[] a = new Point[1000];
int x = a[500].X;   // 0
public struct Point { public int X, Y; }

如果是引用类型,那么元素会是null

Point[] a = new Point[1000];
int x = a[500].X;   // Runtime error, NullReferenceException
public class Point { public int X, Y; }

Indices and Ranges

Indices

索引可以让你引用一个相对数组的末尾的元素,使用^操作符。比如,^1就是获取到数组的最后一个元素,^2是数组的倒数第二个元素

char[] vowels = new char[] {'a','e','i','o','u'};
char lastElement = vowels [^1];     // 'u'
char secondToLast = vowels [^2];    // 'o'

这个操作符其实结果是一个Index类型

Index first = 0;
Index last = ^1;
char firstElement = vowels [first];     // 'a'
char lastElement = vowels [last];       // 'u'

Ranges

Ranges让你可以对一个数组切片,用..操作符

char[] firstTwo = vowels [..2];     // 'a', 'e'
char[] lastThree = vowels [2..];    // 'i', 'o', 'u'
char[] middleOne = vowels [2..3];   // 'i'

操作符的结果是一个类型Range

Range firstTwoRange = 0..2;
char[] firstTwo = vowels [firstTwoRange];   // 'a', 'e'

Multidimensional Arrays

多维数组有两种形式: rectangularjagged

矩形数组表示 n 维内存块,锯齿数组是数组的数组。

Rectangular arrays

int[,] matrix = new int[3,3];

int[,] matrix = new int[,]
{
    {0,1,2},
    {3,4,5},
    {6,7,8}
};

Jagged arrays

int[][] matrix = new int[3][];

int[][] matrix = new int[][]
{
    new int[] {0,1,2},
    new int[] {3,4,5},
    new int[] {6,7,8,9}
};

它是数组的数组,所以在声明的时候,可以声明第一个数组,内部元素都是null,然后内部的数组可以每一个的长度都不一致

Simplified Array Initialization Expressions

char[] vowels = {'a','e','i','o','u'};

int[,] rectangularMatrix =
{
    {0,1,2},
    {3,4,5},
    {6,7,8}
};

int[][] jaggedMatrix =
{
    new int[] {0,1,2},
    new int[] {3,4,5},
    new int[] {6,7,8,9}
};

var vowels = new[] {'a','e','i','o','u'};

var rectMatrix = new int[,]
{
    {0,1,2},
    {3,4,5},
    {6,7,8}
};

var jaggedMat = new int[][]
{
    new int[] {0,1,2},
    new int[] {3,4,5},
    new int[] {6,7,8,9}
};

Bounds Checking

在运行时会对数组做边界检查

Variables and Parameters

一个变量代表的是一个可变的存储位置,它可以是一个本地变量、参数(value,ref,out,in)、字段(实例、静态),或者是一个数组的元素

The Stack and the Heap

栈和堆是存储变量的地方,它们有着不同的生命周期

Stack

栈是用来存储本地变量和参数的。随着方法或者函数的进入和退出,栈会逻辑地增长和收缩。

Heap

堆是对象(即是引用类型)的驻留内存。每当一个对象创建,就会在堆中申请内存,并有一个引用指向这个对象。运行时有垃圾收集器来将对象的占用内存释放。当对象不再被引用之后,就会被垃圾收集器回收

Default Values

可以用关键字default获取任何类型的默认值

Console.WriteLine (default (decimal));

decimal d = default;

Parameters

可以控制参数传递的额外的行为

Passing arguments by value

在默认情况下,C#是值传递,这意味着会copy一个值传递到方法内部,所以在方法的内部,对参数的修改,不会影响到方法外部的变量;但是如果将一个引用使用值传递的方式传递到方法,那么会变成两个变量指向一个对象,所以在方法内部对这个对象的修改,会影响到外部的变量,而如果是给这个引用参数赋值,其实是将这个引用指向其他的对象,对外部变量没有影响。

int x = 8;
Foo (x);

Console.WriteLine (x);

static void Foo (int p)
{
    p = p + 1;
    Console.WriteLine (p);
}
StringBuilder sb = new StringBuilder();
Foo (sb);
Console.WriteLine (sb.ToString());  // test

static void Foo (StringBuilder fooSB)
{
    fooSB.Append ("test");
    fooSB = null;
}

The ref modifier

参数传递的时候,添加了ref关键字在方法签名中,这相当于给实参起了一个别名,它们同时指向同一个内存地址,或者说一个内存地址有两个变量名。注意的是,ref是传递方和声明方都要写

int x = 8;
Foo (ref x);    // Ask Foo to deal directly with x
Console.WriteLine (x);  // x is now 9


static void Foo (ref int p)
{
    p = p + 1;  // Increment p by 1
    Console.WriteLine (p);  // Write p to screen
}

The out modifier

out关键字和ref差不多,除了:

  • 在进入函数之前,它不需要被赋值
  • 在函数返回之前,必须被赋值
Split ("Stevie Ray Vaughan", out string a, out string b);

void Split (string name, out string firstNames, out string lastName)
{
    int i = name.LastIndexOf (' ');
    firstNames = name.Substring (0, i);
    lastName = name.Substring (i + 1);
}

The in modifier

in关键字也是和ref差不多,唯一区别是,in修饰的参数,在方法中是不可变的,如果改了,会有编译错误。

The params modifier

params关键字是用在方法的最后一个参数的修饰符。它允许方法接收指定类型的多个参数。参数的类型必须是数组形式的

int total = Sum (1, 2, 3, 4);

int Sum (params int[] ints)
{
    int sum = 0;
    for (int i = 0; i < ints.Length; i++)
        sum += ints [i];
    return sum;
}

Optional parameters

可选参数,在方法声明中已经给定某个值

Foo();
Foo (23);

void Foo (int x = 23) { Console.WriteLine (x); }

可选参数,不可以用ref或者out修饰符

Named arguments

Foo (x:1, y:2);     // 1, 2

void Foo (int x, int y) { Console.WriteLine (x + ", " + y); }

Ref Locals

int[] numbers = { 0, 1, 2, 3, 4 };
ref int numRef = ref numbers [2];

numRef是numbers[2]的引用,修改numRef,会影响到numbers[2]

而且,Ref locals只能应用在数组元素、字段、或者本地变量,不能应用在属性上

一般是和ref returns一起使用

Ref Returns

可以在方法中返回一个Ref locals,这就是ref returns

static string x = "Old Value";
static ref string GetX() => ref x;

static void Main()
{
    ref string xRef = ref GetX();
    xRef = "New Value";
    Console.WriteLine (x);`
}
// 这也是合法的,只是方法返回值不是ref的
string localX = GetX();     // Legal: localX is an ordinary non-ref variable.
// 可以在返回值添加只读限制
static ref readonly string Prop => ref x;

Target-Typed new Expressions

System.Text.StringBuilder sb1 = new();
System.Text.StringBuilder sb2 = new ("Test");

等价于

System.Text.StringBuilder sb1 = new System.Text.StringBuilder();
System.Text.StringBuilder sb2 = new System.Text.StringBuilder ("Test");

Expressions and Operators

Operator Table




Null Operators

C#提供了三种操作符让操作null值更简单:null-coalescingnull-coalescing assignmentnull-conditional

Null-Coalescing Operator

??操作符。语义是“如果左边的操作数不是null,将它给我;否则,给我另外一个值(右操作数)”

string s1 = null;
string s2 = s1 ?? "nothing";    // s2 evaluates to "nothing"

它同样适用于nullable value types,可空值类型

Null-Coalescing Assignment Operator

??=操作符。它的语义是“如果左操作数是null,那就将右操作数赋值给左操作数”

myVariable ??= someDefault;

这个操作符特别适用于延迟加载的属性

Null-Conditional Operator

?.操作符。它允许你在调用方法或者访问成员时,和正常的dot操作符一样,除了当你的左操作数是null的时候,整个表达式的结果是null,而不是抛出异常NullReferenceException

System.Text.StringBuilder sb = null;
string s = sb?.ToString();  // No error; s instead evaluates to null

同样还有个索引访问的时候,也可以使用?[]

Statements

Selection Statements

The switch statement

switch语句,语句是没有结果的

switch (cardNumber)
{
    case 13:
        Console.WriteLine ("King");
        break;
    case 12:
        Console.WriteLine ("Queen");
        break;
    case 11:
        Console.WriteLine ("Jack");
        break;
    default:
        Console.WriteLine (cardNumber);
        break;
}

还可以匹配类型

switch (x)
{
    case int i:
        Console.WriteLine ("It's an int!");
        Console.WriteLine ($"The square of {i} is {i * i}");
        break;
    case string s:
        Console.WriteLine ("It's a string");
        Console.WriteLine ($"The length of {s} is {s.Length}");
        break;
    default:
        Console.WriteLine ("I don't know what x is");
        break;
}

Switch expressions

switch表达式,是有计算结果的,可以作为右值赋值给左值

string cardName = cardNumber switch
{
    13 => "King",
    12 => "Queen",
    11 => "Jack",
    _ => "Pip card" // equivalent to 'default'
};

Iteration Statements

foreach loops

对一个enumerable对象迭代。

foreach (char c in "beer")
    Console.WriteLine (c);

Miscellaneous Statements

using语句,为IDisposable对象在finallyblock里面调用Dispose方法

lock语句是一个是调用 Monitor 类的 Enter 和 Exit 方法的快捷方式

Namespaces

命名空间是类型名称的域。类型通常组织为分层命名空间,这样容易防止命名冲突

类型就包裹在namespaceblock下

namespace Outer.Middle.Inner
{
    class Class1 {}
    class Class2 {}
}

等价于

namespace Outer
{
    namespace Middle
    {
        namespace Inner
        {
            class Class1 {}
            class Class2 {}
        }
    }
}

using static

导入了这个类型的所有公开静态成员

Rules Within a Namespace

声明在外部的命名空间,可以不需要导入就能让它的内部的命名空间访问

namespace Outer
{
    class Class1 {}

    namespace Inner
    {
        class Class2 : Class1
        {}
    }
}

Aliasing Types and Namespaces

起别名

using PropertyInfo2 = System.Reflection.PropertyInfo;

Advanced Namespace Features

Extern

当程序引用的两个库,都有相同的命名空间和类型名称时,可通过以下方式解决

<ItemGroup>
    <Reference Include="Widgets1">
        <Aliases>W1</Aliases>
    </Reference>
    <Reference Include="Widgets2">
        <Aliases>W2</Aliases>
    </Reference>
</ItemGroup>
extern alias W1;
extern alias W2;
W1.Widgets.Widget w1 = new W1.Widgets.Widget();
W2.Widgets.Widget w2 = new W2.Widgets.Widget();

标签:10,Console,c#,int,WriteLine,类型,new,ref,随记
来源: https://www.cnblogs.com/huangwenhao1024/p/16438675.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有