LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

learn_CSharp

2022/4/12

学习C#

想用unity开发一些有意思的上位机软件,先学习一下C#

环境搭建

我选择在JetBrains的Rider进行C#的学习,直接在Jetbrains Toolbox安装

C#依赖于 .Net ,需要安装DotNet-SDK,链接:https://dotnet.microsoft.com/zh-cn/download

一键安装,环境变量也配置好了,可以打开命令行cmd确认是否安装完成

dotnet --version

打开Rider,新建项目,选择 .NET/ .NET Core>Console Application,然后起个名字,指定路径

自动出现HelloWorld程序,编译运行即可

Console.WriteLine("Hello, World!");

学习笔记

C# Language Specification:https://docs.microsoft.com/en-us/dotnet/csharp/

中文版C#文档的:https://docs.microsoft.com/zh-cn/dotnet/csharp/

C语言中文网:http://c.biancheng.net/csharp/


以下并没有包含完整C#入门知识点,只是我挑选着记录的笔记

一些概念

首先我们创建项目时就有两个概念:项目(Project)和解决方案(Solution),两者默认同名

项目可以理解成你最后要实现的总功能,比如Web应用程序。

解决方案可以理解成存放不同功能的容器,当开发的总项目较为复杂时会包含很多功能

用Rider创建项目后会自带Program.cs,这个文件是程序的入口。


程序入口

在学习之前我有大致了解,C#是完全面向对象的语言。

在测试HelloWorld的时候就有疑问,为什么不需要在类里放Main函数作为程序入口点

搜索后发现,自C# 9起,使用顶级语句可以省略Main方法,编译器会自动生成类和Main入口点


HelloWorld

以HelloWorld为例,实现多种方式运行

首先最简单的就是使用顶级语句:

//Console是命名空间System下的一个类 用于命令行输出 我学习时已经不需要using System;了
Console.WriteLine("Hello, World!");

其次是使用类的Main作为入口,参考教程中实现如下:

namespace LearnCsharp
{
    class HelloWorld
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

关于namespace,也可以写成文件范围的命名空间声明

这样整个文件都属于一个命名空间,常用于一个文件中有多个类的情况,如下:

namespace LearnCsharp;
class HelloWorld
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}

这个代码是可以编译运行的,但是Rider中会有警告

1.会提示类没有实例化,Class ‘HelloWorld’ is never instantiated

2.会提示Main缺少private,Inconsistent modifiers style: missing ‘private’ modifier


下面是我觉得实现起来思路比较顺的一种写法,

新建一个类,源文件名为HelloWorld.cs

namespace LearnCsharp;

public static class HelloWorld
{
    public static void Print()
    {
        Console.WriteLine("Hello, World!");
    }
}

Program.cs中调用

using LearnCsharp;

HelloWorld.Print();

static

static表示静态

不需要实例化就可以调用的方法为静态方法,静态方法中只能使用静态类成员

一般不需要实例化的类就定义为静态类,其所有方法一定是静态方法


字符串

字符串采用string类型,常使用const描述符。

字符串拼接可以使用+,可以直接拼接数字。

字符串前缀@,表示后续字符串不转义,常用于路径前,相当于python的字符串前缀r

字符串前缀$,可用于字符串格式化,相当于python的字符串前缀f


访问修饰符和修饰符

访问修饰符:

public:可以被任何代码访问

private(默认):可以被同一类中代码访问

internal:可以被同一项目访问

protected:只能由类或派生类中的代码访问


修饰符记录一下readonly和const

const修饰的在声明时就要设置值

而readonly修饰的可以在类的构造函数时设置值


定义字段时顺序如下:

访问修饰符 修饰符 数据类型 字段名;


get和set

首先介绍一下字段与属性

字段:一般是private,用于内部数据交互,若需要提供给外部时,需要封装为属性

属性:一般是public,在面向对象设计中主要使用属性描述对象的静态特征


get和set就是可以将字段封装为属性,可以设置获得值以及设置值时做的操作

C#中的get和set类似于Python中@property和setter,实现如下:

public class Student
{
    private int _id = 1;
    public int Id
    {
        get
        {
            return _id;
        }
        set
        {
            _id = value;
        }
    }
}

如果不做逻辑判断,可见上述字段只是作为一个中间变量

为了简化,可以采用自动属性设置方式,写成如下:

public class Student
{
    public int Id { get; set; } = 1;//为了区分字段和属性 应该避免写成public int Id = 1;
    // public int Id { private get; set; } = 1; //只写
    // public int Id { get;  } = 1; //只读 
}

属性的作用:

1.可以加入判断,避免非法数据

2.可以根据情况设置只读或者只写

3.调用属性时进行运算,不需要对字段进行操作


构造和析构

构造方法前需要加访问修饰符,析构则不需要。

我试了一下,写个析构,断点调试,并没有运行。

网上有网友说,C#析构函数不一定会执行,官方不推荐在析构函数释放堆栈。


引用参数和输出参数

这两个参数都是按地址传递,使用后参数的值会改变,感觉类似C++的引用

引用参数ref,方法定义与方法调用都需要加上ref,ref参数必须初始化,例如两数交换实现如下:

namespace LearnCsharp;

public static class Operation
{
    public static void Swap(ref int  a, ref int  b)
    {
        a ^= b;
        b ^= a;
        a ^= b;
    }
}
using LearnCsharp;

var a = 5;
var b = 10;
Operation.Swap(ref a,ref b);
Console.WriteLine(a);
Console.WriteLine(b);

输出参数out,方法定义与方法调用都需要加上out,out参数不能初始化,例如传入函数初始化:

namespace LearnCsharp;

public static class Number
{
    public static void Init(out int  a)
    {
        a = 5;
    }
}
using LearnCsharp;

Number.Init(out var a);
Console.WriteLine(a);

lambda

格式为:

访问修饰符 修饰符 返回值类型 方法名(参数列表) => 表达式;

例如实现两数相加:

public static int Add(int a, int b) => a + b;

部分类

在class之前加上partial的类是部分类,部分类就是一个类的一个部分,

所有部分类合起来就是完整的一个类,哈哈哈哈感觉有点废话


装箱和拆箱

将值类型转换为引用类型的操作称为装箱,将引用类型转换成值类型称为拆箱

例如int转string称为装箱,string转int称为拆箱

//装箱
const int  n = 100;
var s = n.ToString();
Console.WriteLine(s);
//拆箱
const string s = "100";
var n = int.Parse(s);
Console.WriteLine(n);

数组

string[] stringArray = {"123", "456", "789"}; //数组声明 
Console.WriteLine(stringArray[0]); //下标访问
Console.WriteLine(stringArray.Length); //数组长度

var numArray = new int[5];//初始化五个零的数组
Console.WriteLine(numArray[0]); //下标访问
Console.WriteLine(numArray.Length); //数组长度

foreach

foreach用于遍历,使用起来类似Python的for循环,例如遍历数组:

int[] numArray = {1,2,3,4,5,6,7,8,9,0};
foreach(var number in numArray)
{
    Console.WriteLine(number);
}

枚举

枚举和C语言类似,但是他每一个枚举值都是表示字符串,可以通过转换获得对应的值

所以我们在代码中也能喜闻乐见的看到,中文没有被扩在引号内而且不会报错了

namespace LearnCsharp;
public enum Color : int
{
    红,
    蓝,
    黄
}
using LearnCsharp;


Console.WriteLine((int)Color.红);
Console.WriteLine((int)Color.蓝);
Console.WriteLine((int)Color.黄);

Object类

Object类是所有类的父类,类定义时默认继承Object类

Object 类中提供了 4 个常用的方法

Equals用于判断是否引用了同一个对象

GetHashCode用于返回当前实例的哈希值

GetType用于返回当前实例的类型

ToString用于返回一个对象实例的字符串


继承

继承父类只需要在定义类时在它后面加个冒号以及父类,调用父类的方法需要用base关键字

virtual 方法表示将会在继承后重写

override 修饰符用于扩展基类 virtual 方法,相当于重新定义父类中方法的内容

new 修饰符用于隐藏可访问的基类方法,相当于定义新方法


abstract修饰符代表抽象的,

virtual和abstract都是用来修饰父类的,子类重新定义覆盖父类。

virtual修饰的方法一定要有实现,而abstract一定没有实现

virtual可以被子类重写,而abstract一定被子类重写


sealed 修饰符代表密封的,只能出现在子类,并且是重写父类方法,必须与 override 关键字一起使用

sealed修饰的类不能被继承,修饰的方法不能被重写


接口

接口用interface修饰符来定义,接口名称常用I开头的单词构成。

接口中不能有字段,接口中不能有方法实现,并且成员不能被修饰符修饰,接口的方法通过类继承来实现。

关于接口和抽象类,有个比喻很好理解:

飞机和鸟都会飞,他们都继承了接口飞,但直升飞机属于飞机抽象类,鸽子属于鸟抽象类。


集合

ArrayList是动态数组,与数组操作类似,但是功能丰富很多

Queue是队列,元素从尾部插入,从头部移除

Stack是栈,元素先进入的到最底部,也就是先进后出

Hashtable是哈希表(散列表),存放键值对

SortedList是有序列表,存放键值对,按值排序

使用之前需要using System.Collections;