学习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.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;
}
属性的作用:
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;