从 .NET, 到 WPF, 以及 C#

印象

.NET 是纯正的面向对象程序构建方式, using xxx引入程序包类似于 C/C++ 的#include引入头文件, 但是引入过程更加像引入类: 通过点运算符, 你可以引入程序包的不同层级, 并且不必担心头文件和源文件之间的复杂包含关系.

.NET 把项目与项目之间的联动也以类似于对象关系的方式实现. 你可以在一个项目中”引入”另一个项目, 这将创建一种类似于类之间的依赖关系, 如果引入的项目生成的是可执行程序, 这相当于使用外部程序; 如果引入的项目生成的是类库, 这相当于使用动态链接库.

构建项目

参照VScode官方教程VS官方教程

最基础的, 需要选择 .NET Framework 的版本, 在这里下载需要的版本或者最新的版本. 如果是要打开运行已有的项目的话, 最好按照项目中记录的版本号, 选择下载对应的版本. 随着版本更新, .NET 的变化还是很大的.

使用 VScode 创建项目

使用 Visual Studio Code 来编写调试程序能更好地了解 .NET 项目组成原理. 注意是通用的 .NET 项目, 而不是 Windows 上专用的 WPF 项目.

想要开始的话: 至少要安装C# Dev Kit拓展, 它将提供自动构建项目的功能, 否则也可以通过 dotnet 命令行来进行构建项目, 引入项目等操作. 其次还可以安装 .NET 拓展包, 对编程有一定帮助. 自己用的时候安装了这几个:

.NET开发用插件

.NET 项目结构浅析

使用拓展提供的命令(.NET: New Project)构建好项目之后, 项目结构中需要理解的地方:

项目中一些文件的内容意义

如上图所示,已发布的输出包含以下文件:

  • HelloWorld.deps.json
    这是应用程序的运行时依赖项文件. 它定义运行应用所需的 .NET 组件和库(包括包含应用程序的动态链接库). 有关详细信息,请参阅 运行时配置文件.
  • HelloWorld.dll
    这是应用程序的依赖于框架的部署版本. 若要运行此动态链接库,请在命令提示符处输入 dotnet HelloWorld.dll. 运行应用的方法适用于安装了 .NET 运行时的任何平台.
  • HelloWorld.exe(HelloWorld 在 Linux 或 macOS 上).
    这是应用程序的依赖于框架的可执行文件版本. 该文件特定于操作系统.
  • HelloWorld.pdb(对于部署是可选的)
    这是调试符号文件. 无需与应用程序一起部署此文件,但应在调试应用程序的已发布版本时保存该文件.
  • HelloWorld.runtimeconfig.json
    这是应用程序的运行时配置文件. 它标识了您的应用程序是为哪个 .NET 版本构建和运行的. 还可以向其添加配置选项. 有关详细信息,请参阅 .NET 运行时配置设置.

值得注意: 发布类库时需要指定TargetFramework, 在xxx.csproj(xxx是项目名)中修改, 它决定了能使用这个类库的 .NET Framework 版本.

使用第三方库的时候, 就像使用非C++标准库一样, 需要把第三方类库”引入”项目(当然也可以在项目内新建一个类库, 引入到另一个类库). 在 Visual Studio Code 中, 操作流程大致如下:

添加类库以及引用的步骤

不难看出. .NET 软件框架的主要特征, 就是以.NET项目为基础单位, 每个项目单独编写和编译, 其主要产物可能是链接库(.dll)可执行程序(.exe). .NET 项目之间再相互引用, 组合而成一整个项目.

而 .NET Framework 则为全局的 .NET 项目提供了程序包, 例如System.IO程序包控制输入输出, System.Text.Json程序包提供Json相关功能… WPF 作为 .NET 类型的子集存在, 其主要特征就是提供了 System.Windows 程序包.

使用 VS 创建项目

如果是在 Visual Studio 2022 (或者其他版本), 创建 WPF 项目相当简单, 只需安装好负载(.NET桌面开发)要选好项目模板(WPF应用程序)就行.

如果是要创建供其他项目使用的类库的话, 选 WPF 类库.

选择项目模板

创建好项目后, 项目配置, 编译运行均为图形界面操作, 与C++项目基本一致, 不再赘述.

使用 NuGet 共享包

类似于 .NET 框架的包管理器.

目前好像直接用dotnet add package Newtonsoft.Xxxx就可以引入nuget.org的第三方包了. NuGet 的介绍见官方教程. 这里先不写.

在 VS 中, 我们可以直接通过 项目>管理 NuGet 程序包 来图形化管理第三方包.

我们还可以在nuget.org注册账号, 上传自己制作的包…

编写内容

参见官方教程

因为涉及到图形界面元素组织, 所以一般用 Visual Studio 进行编写.

窗口

WPF 应用使用 xaml 文件表示窗口, 以类似 xml 文件的形式编写界面元素信息. 通常来说, xaml 文件下还绑定着一个 .xaml.cs 文件, 其用以响应元素上发生的事件.

可以打开 VS 的工具箱窗口, 并选择 xaml 文件, 然后通过拖动元素的方式来新建元素, 再使用属性窗口来详细设定元素参数.

直接在可视化窗口中双击按钮等元素, 可以快速创建该元素的回调函数(正式运行时, 点击该元素会执行的函数), 也可以右键并跳转到改元素的对应函数, 不只是回调函数, 也可以设置其他的绑定内容.

C#语言高级功能

C# 提供了一些额外功能来帮助构建程序, 它们大多是对某些设计的简化, 十分有用. 适当了解对看懂别人的项目或者直接写项目都有帮助

委托 Delegate

参考C# 委托(Delegate) | 菜鸟教程.

在 C# 中,委托(Delegate) 是一种类型安全的函数指针,它允许将方法作为参数传递给其他方法。

C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量,引用可在运行时被改变。

委托在 C# 中非常常见,用于事件处理、回调函数、LINQ 等操作。

所有的委托(Delegate)都派生自 System.Delegate 类。

我来一句话总结的话, 就是 类型安全的回调函数用法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 声明委托的语法:
// public delegate <return type> <delegate-name> <parameter list>
// public delegate 返回类型 委托名 (参数类型 参数名, ...);

// 定义一个 (cmd, rcv) -> bool 类型的委托
public delegate bool SqlCmd(string cmd, List<string> rcv);

// 假设别处已经书写了这么一个 "回调函数"
public bool SqlGetFirstLine(string cmd, List<string> rcv)
{
// ... 发送sql命令, 并保存收到的第一行数据到 rcv
return ret;
}
public bool SqlGetAllLine(string cmd, List<string> rcv)
{
for(...)
{
// ... 发送sql命令, 并保存收到的第一行数据到 rcv
}
return ret;
}

// 实例化和使用
public static Main()
{
// 将回调函数作为"委托"对象的初始化参数
SqlCmd SqlGetDeleget = new SqlCmd(SqlGetFirstLine)
// 执行委托
List<string> lines;
SqlGetDeleget("select * where name equals ker0123", lines);
// 切换实际调用函数, 再执行委托
SqlGetDeleget = new SqlCmd(SqlGetAllLine);
SqlGetDeleget("select * where name equals ker0123", lines);
}

相同类型的委托, 还能够用+, -, +=, -= 拼接成新的委托, 或者排除其中某个委托, 达到一条命令执行两个同类委托的效果.

事件 Event 以及 “发布-订阅” 模型

CMake + clang, 跨平台项目编译.

前言

随着对 C/C++ 的了解, 发现其作为一门语言, 有不止一个编译器. Unix like 系统上的 GCC, Windows 平台上的 MSVC, 以及跨平台的 CLANG.

GCC 的使用最为广泛, 但是其有很重的平台属性(Unix), 虽然在 Windows 平台上有它的移植项目 MinGW(Minimalist GNU for windows), 但是使用其的软件不是很多: Windows 原生软件直接使用 MSVC, 而跨平台软件, 例如基于 Qt 的软件, 则使用的 Clang.

因此, 学习Clang编译器的使用是一件很有必要的事.

至于 CMake, 主要是跨平台的项目编译太离不开它了, 而我自己编程这么久还不会写 makefile. 因此在这里一并学了.

另外该文章还包含的有 lua 脚本语言. 想要在 C/C++ 项目中植入脚本, python 是一个选择, 但据我了解, lua 比 python 快的多. 总之, 这个测试项目用 lua 作为外部库, 实践库导入等操作

以下是这几个项目的相关官方链接:

CMake

安装

直接在官网下载最新版本的可执行文件(文件名为cmake-4.0.2-windows-x86_64.msi). 傻瓜式安装.

安装位置选取在D:\bin\CMake, 可执行文件位于D:\bin\CMake\bin\cmake.exe. 这个位置在配置项目时可能会用到.

使用

按照这个教程进行的学习.

小贴士

  • cmake 语法大小写不敏感.
  • #注释, #[[...]]块注释.

按实例解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 最低版本 4.0.0
cmake_minimum_required(VERSION 4.0.0)

# 项目名称 Lua-text version 0.1.0
project(Lua-test VERSION 0.1.0)

# 项目C++版本
set(CMAKE_CXX_STANDARD 17)

# 项目目录
set(SOURCE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/src)
set(INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/inc)
set(LIBRARY_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/lib)

#
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

include_directories(${PROJECT_SOURCE_DIR}/include)

link_directories(${PROJECT_SOURCE_DIR}/lib)

link_libraries(calc)
add_executable(app ${SRC_LIST})

Godot 学习记录: 与 Unity, UE 完全不同的结构思路

知识点

动态语言与鸭子类型

官方文档

C#也算作动态语言. 与C++, java等静态语言有几个很值得注意的不同:

  • 一些在静态语言编译时能够检查出来的错误, 在动态语言中无法检测出来. 只会在运行的时候报错
  • 动态语言, 包括C#和GDScript, 默认对基础类型的传递是值传递, 默认对其他所有类型是引用传递.

鸭子类型可以视为静态语言的简化和解耦. 对象A调用对象B的某个方法时, 无需检测复杂的继承关系是否使对象B实现了该方法, 会直接尝试调用.

“当我看到一只鸟像鸭子一样走路, 像鸭子一样游泳, 像鸭子一样呱呱叫时, 我就管这只鸟叫鸭子”

但是如果对象B实际上没有实现该方法的话, 程序就会在运行时报错. 按照之前写Unity的经验, 可以举例: 如果某个游戏对象上的脚本尝试获取一个游戏对象上不存在的组件时, 就会报错. 如果再尝试进一步调用组件的方法, 甚至可能会结束程序.

因此, 一般在调用前用如下代码检查是否存在该方法, 再进行调用.

1
2
3
func _on_object_hit(object):
if object.has_method("smash"):
object.smash()

安全行

Gpdot提供了这个工具来在编写代码时告诉程序员, 某行歧义代码是否能正常执行.

1
2
3
$Timer.start()
# => 安全
($Timer as Timer).start()

里氏代换原则 协变与逆变

派生类(子类)对象可以在程序中代替其基类(超类)对象.

要达到这个原则, 简单来说, 子类的输出包含的数据只能比基类更多, 子类能接收的输入只能比基类更宽泛.

协变: 继承方法时, 你可以为子类方法指定一个比该子类方法的父类方法更为具体的返回值类型(子类型). 这保证了子类的输出包含原有输出的所有数据, 只会更多.

逆变: 继承方法时, 你可以为子类方法指定一个比该子类方法的父类方法更不具体的参数类型(超类型). 这保证了子类能够接收原来传给基类的所有数据, 只会能接收更多种.

一句话小技巧

  • 可以用:=来使左边的变量自动推导类型, 达到静态编程的效果.
  • 可以用as xxx来转换返回值类型.
  • Array,