0
点赞
收藏
分享

微信扫一扫

【To .NET】C#基础较难易混知识点整理


​大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨‍🌾!​

👩‍🌾“人生苦短,你用Python”,“Java内卷,我用C#”。

从Java到C#,不仅仅是语言使用的改变,更是我从理想到现实,从象牙塔到大熔炉的第一步。.NET是微软的一盘棋,而C#是我的棋子,只希望微软能下好这盘棋,而我能好好利用这个棋子。

一、编程基础篇

1、空值处理

如下代码会报错:CS0037-无法将 null 转换为“int”,因为后者是不可为 null 的值类型。

int nullProp = null;
Console.WriteLine($"nullProp = {nullProp}");

因为像int和DateTime这样的值类型必须总是有值的。像string这样的引用类型可以为空。

string nullProp = null;
Console.WriteLine($"nullProp = {nullProp}");

一般情况下不允许变量有空值,可以通过设置项目级选项来启用该特性。

项目级别:

<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>

文件级启用与禁用:

# nullable enable
# nullable disable

当我们启用了可空引用类型,并希望为引用类型分配空值,我们需要在类型声明后添加?。

?.则是用于检查变量是否为空,是if(不为空)的语法糖。

# nullable enable
using System;

namespace fineyun
{
internal class Program
{
static void Main(string[] args)
{
string? nullProp = null;
string? notNullProp = null;
Console.WriteLine($"nullProp = {notNullProp?.Length}");
}
}
}

二、控制程序流和转换类型

1、条件逻辑运算符与逻辑运算符

&&是条件逻辑运算符中的"并且",其会先判断左操作数是否为true,若为false则结束语句不执行右操作数的执行。我们可以用该特点写较为简洁高效的代码。除此条件逻辑运算符还有||。

bool DoNotDo = false;
DoNotDo && Do();

而逻辑运算符对布尔值进行操作,有|表示或,&表示且,^表示异或,不同为true,相同则为false,还有!表示非。

2、圆整规则

C#中进行浮点数向int类型强制转换时,圆整规则如下:

  • 小数部分小于0.5,向下取整
  • 小数部分大于0.5,向上取整
  • 小数部分等于0.5,奇数向上取整,偶数向下取整

3、字符串的转换

字符串转换为数字或日期和时间,可以使用相应类型的Parse方法。

int i = int.Parse("1");
DateTime today = DateTime.Parse(4 july 2022);

一般使用Try Parse避免异常,out代表赋值输出的变量,方法返回bool类型。除此还可以使用System.Convert方法进行转换。

int i;
int.TryParse("哈哈哈",out i);

4、捕获异常

要获取可能发生的任何异常信息,可以catch类型为Exception的变量,捕获特定异常则catch相应的异常类型。

catch(Exception ex){
WriteLine($"{ex.Message}")
}

5、检查溢出

checked语句告诉.NET。要在发生溢出时抛出异常,而不是沉默不语。因为发生溢出时,会出现取值异常,不会进行报错。

checked{
int x = int.MaxValue - 1;
}

我们可以通过catch OverflowException异常进行相应的处理。

三、面向对象

在C#中,可使用C#关键字class(通常)或struct(偶尔)来定义对象的类型。封装是对象相关的数据和操作的集合,组合是指物体由什么构成,聚合指什么可以与对象相结合,继承是指从基类或超类派生子类来实现重用,抽象是提取对象的核心。

1、类的成员

C#中类的成员十分不同于Java,Java中类的成员无非变量(属性)和方法。C#中的成员可分为两类:字段和方法。

其中字段:

  • 常量字段:const修饰的字段
  • 只读字段:readonly修饰的字段
  • 事件:数据引用一个或多个方法,方法在发生事件时执行

其中方法:

  • 构造函数:使用new关键字分配内存和实例化类时执行的语句
  • 属性:获取或设置数据时执行的语句
  • 索引器:使用数组语法[]获取或设置数据时执行的语句
  • 运算符:对类型的操作数使用+和/之类的运算符执行的语句

2、元组

元组类型方法:

public (string,int) GetKeyValue(){
return ("key",1);
}

元组类型:

(string,int) keyValue = GetKeyValue();

3、参数的定义

(1)可选参数

除了重载方法以外,我们还可以通过可选参数的方式简化方法。通过在方法的参数列表中指定默认值,可以使参数成为可选。

public string OptionalParamenters(string name = "未来村村长",
int id = 0,
bool isHandsome = ture){
return $"{name}{id}{isHandsome}"
}

OptrionalParamenters("未来村村民",2);//name和id会改变,isHandsome不会改变

还可以通过使用命名参数改变传参的顺序。

OptrionalParamenters(id:2,name:"未来村村民");

(2)控制参数的传递方式

但传递参数给方法时,参数可以通过三种方式进行传递:

  • 值:传入变量的副本,方法不能对变量进行修改
  • ref引用:传入变量的引用,方法会对变量进行修改
  • 作为out参数:传入的变量会被方法中的out 参数替换

4、Partial关键字

关键字partial是一个上下文关键字,其作用是将一个类(或class、struct、interface)分解到不同的类文件中,在最终编译时可将其合并。局部类型的各个部分一般是分开放在几个不同的.cs文件中,但C#编译器允许我们将他们放在同一文件中.

局部类型上的修饰符:

  • 一个类型的各个部分上的访问修饰符必须维持一致性
  • 如果一个类型有一个部分使用了abstract修饰符,那么整个类都将被视为抽象类
  • 如果一个类型有一个部分使用了 sealed 修饰符,那么整个类都将被视为密封类
  • 一个类的各个部分不能使用相互矛盾的修饰符,比如abstract和sealed

局部类型的基类和接口:

​ 一个类型的各个部分上指定的基类必须一致。某个部分可以不指定基类,但如果指定,则必须相同
局部类型上的接口具有“累加”效应。

5、属性

Java中将变量和方法分离,并建议通过编写getter和setter方法来进行变量的访问和设置。而在C#中使用属性来完成该工作。

public string prop{get; set;}
//只读属性
public string prop{get;}

6、索引器

索引器允许调用代码使用数组语法来访问属性,字符串类型就定义了索引器,这样通过[]就可以访问字符串中的字符。

我们来看ArrayList中的索引器:

public virtual Object this[int index] {
get {
if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
Contract.EndContractBlock();
return _items[index];
}
set {
if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_Index"));
Contract.EndContractBlock();
_items[index] = value;
_version++;
}
}

一般的索引器格式为:

public 返回类型 this[int index]{
get{
return item[index];//item为该类的数组属性
}
set{
item[index] = value;
}
}

四、接口和继承

1、局部函数

C#7.0开始,可以在函数中定义局部函数。

public int c(){
retrun F();
int F(){
return 0;
}
}

2、委托和事件

方法是对象可以执行的操作,事件是发生在对象上的操作,事件建立于委托。

(1)委托

委托是调用方法的另一种方式,委托为方法的调用提供了灵活性,还能用于多线程允许多个操作并行执行。

在C#中使用委托具体步骤如下:

  • 声明一个委托,其参数形式一定要和想要包含的方法的参数形式一致,返回值也需要一致;
  • 创建委托对象并将所希望的方法包含在该委托对象中;
  • 通过委托对象调用包含在其中的各个方法;
  • 可通过+,+=,-,-=对代理对象进行组合执行。

//声明一个委托
delegate int MyDelegate();
//定义需要被代理的类
class Myclass{
//定义被代理方法
public int M1(){
return 0;
}
public static int M2(){
return 0;
}
}
//Main类
class Test{
static void Main(){
//定义被代理对象
Myclass w = new MyClass();
//实例化代理对象,传入相关方法
MyDelegate p = new MyDelegate(w.M1);
p();//通过代理对象(),进行代理方法的执行
//可直接代理静态方法
MyDelegate b = new MyDelegate(Mycalss.M2);
b();
//可通过+,+=,-,-=对代理对象进行组合执行
Mydelegate g = p + b;
g()

}
}

微软定义了两个预定义的委托可用于事件。

public delegate void EventHandler(object sender,EventArgs e);
public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e);

(2)事件

事件的创建与执行流程可分为:事件的声明、事件的预定和取消、事件的发生。

事件的声明:

[修饰符] event 委托名 事件名;

事件的预定和取消:

事件名 += new 委托名(方法名);
事件名 -= new 委托名(方法名);

事件的发生:

事件名(参数);

3、内存的分配

内存有两种:堆内存和栈内存,在现代操作系统,栈和堆可以位于物理或虚拟内存中的任意位置。

使用class定义类型时,是在定义引用类型。这意味着用于对象本身的内存是在堆上分配的,只有对象的内存地址存储在栈上。

使用struct定义类型时,是在定义值类型,意味着用于对象本身的内存是在栈上分配的。

4、成员的隐藏和覆盖

子类继承超类时,若在子类中重写了与超类同名的方法或者属性,不会报错但会发出覆盖警告,我们可以在方法返回参数类型前加上new表示我们是故意为之。

与其隐藏方法,不如直接覆盖。我们可以在基类中通过virtual关键字来重写方法,然后在子类中通过override来进行方法的覆盖。

若方法不希望被继承或覆盖,我们可以使用sealed关键字来防止别人继承自己的类。

5、构造函数与继承

与普通的方法不同,构造函数不是继承的,必须显式地声明和调用System.Exception中的base构造函数实现。


举报

相关推荐

0 条评论