C#.Net筑基-基础知识
<p><img src="https://img2024.cnblogs.com/blog/151257/202405/151257-20240502170239383-654573809.png" alt="image.png"></p><hr>
<h1 id="c">01、C#基础概念</h1>
<h2 id="c-1">1.1、C#简介</h2>
<p>C# (读作C Sharp)是由微软公司开发的一种面向对象、类型安全、高效且简单的编程语言,最初于 2000 年发布,并随后成为 .NET 框架的一部分。所以学习C#语言的同时,也是需要同步学习.NET框架的,不过要要注意C#与.NET的对应版本。</p>
<p><img src="https://img2024.cnblogs.com/blog/151257/202405/151257-20240502170239299-489644671.png" alt="image.png"></p>
<p>C#语言和Java类似,是一门简单易用、应用广泛的高级编程语言。结合了面向对象编程、事件驱动、泛型编程、异步编程等众多现代化编程概念,属于编译性语言。主要特点:</p>
<ul>
<li><strong>面向对象</strong>:封装(类与对象)、继承(类继承、接口继承)、多态等(类继承、多接口继承实现)。</li>
<li><strong>类型安全</strong>:强类型安全,在编译时检测,提高代码可靠性。</li>
<li><strong>交互性</strong>,易于各种语言交互,如VB、F#、C++、JavaScript、Python等。</li>
<li><strong>GC管理</strong>:自动内存管理,C# 采用垃圾回收机制,无需申请、释放内存,减少内存泄漏风险。</li>
<li><strong>开源跨平台</strong>:.NETCore框架是开源跨平台的,支持多种操作系统。</li>
<li><strong>强大的标准库</strong>,C#拥有丰富的标准类库(.NET Framework或.NET Core),内置各种功能和工具。</li>
<li><strong>宇宙第一开发IDE</strong>: Visual Studio 提供了强大的开发、调试和设计工具。</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/151257/202405/151257-20240502170239533-891426488.png" alt="image.png"></p>
<p>.NET Framework最高支持C#语法版本是<code>C#7.3</code>、<code>.NET Standard 2.1</code>,可以基于该版本学习,后面的版本可以根据需要学习新增特性即可。</p>
<p><img src="https://img2024.cnblogs.com/blog/151257/202405/151257-20240502170239730-1560596540.png" alt="image.png"></p>
<blockquote>
<p>图来源:C#.NET体系图文概述</p>
</blockquote>
<h2 id="section">1.2、开发环境</h2>
<ul>
<li><strong>运行环境</strong>:安装.NET SDK:下载 .NET, 下载.NET Framework</li>
<li><strong>开发环境</strong>:开发IDE工具安装 Visual Studio ,内置很多开发套件,及多个版本的SDK。</li>
</ul>
<blockquote>
<p>📢 推荐安装<code>Enterprise</code> 企业版!功能最全。开发工具了解:《Visual Studio工具使用入门》</p>
</blockquote>
<p><img src="https://img2024.cnblogs.com/blog/151257/202405/151257-20240502170239490-852284438.png" alt="image.png"></p>
<h2 id="hello-world">1.3、Hello World</h2>
<pre><code class="language-csharp">using System; //引用using
namespace ConsoleApp_Net48//申明命名空间
{
internal class Program//定义类
{
static void Main(string[] args) //方法,控制台入口函数
{
Console.WriteLine("Hello World!");//控制台打印输出
Console.ReadLine();
}
}
}
</code></pre>
<ul>
<li><strong>using</strong> 引用命名空间资源。</li>
<li><strong>namespace</strong> 命名空间 :一组代码资源(类、结构、枚举、委托等)的集合。</li>
<li><strong>class</strong> 类:定义一个类,C#中最常用的代码组织单元。</li>
<li><strong>方法</strong>:特定功能的代码块,有输入和输出(也可为空)。</li>
</ul>
<hr>
<h1 id="section-1">02、基础语法</h1>
<p>C#代码以行为单位,(半角)分号<code>;</code>结尾,花括号<code>{ 代码块 }</code>为一个独立的代码区域。</p>
<p><img src="https://img2024.cnblogs.com/blog/151257/202405/151257-20240502202909903-1859625104.png" alt="image"></p>
<h2 id="section-2">2.1、变量申明</h2>
<p><strong><code>变量类型变量名 = 值</code></strong>,变量就是对象值的名字,就像人的名字一样,通过变量来访问具体的对象值。变量可以是局部变量、参数、字段、数组、对象实例、委托等。</p>
<ul>
<li>申明变量、赋值可以一次性,也可分开,也可以一次性申明多个变量。</li>
<li>变量的使用前必须初始化(赋值),使用未赋值的变量会引发异常。</li>
<li>同一作用域内,一个变量名只能申明一次,不可重复。</li>
<li>字符串用<code>“双引号”</code>,单个字符用<code>'单引号'</code>。</li>
</ul>
<blockquote>
<p>也可以用<code>var</code>申明,编译器通过值类型推断其具体变量类型,因此申明时必须赋值,var是一个语法糖。</p>
</blockquote>
<pre><code class="language-csharp">int age; //先申明,后赋值
age = 12;
float weight = 55.55f;
double height = 188.88d; //末尾可以不用带d,默认就是double
var name = "sam";
var lastName = 'T';
string f1, f2, f3 = "F3"; //申明了3个变量,对f3赋值了
var user = new User(); //创建一个User对象实例
User user2 = new User();//创建一个User对象实例
</code></pre>
<h2 id="section-3">2.2、代码风格</h2>
<p>C#代码的命名风格大多为驼峰命名为主,相对比较统一,不像前端那么麻烦,HTML、CSS、JS、URL各不相同。</p>
<ul>
<li><strong>区分大小写</strong>,字母、数字、下划线组成,不能数字开头,不能是关键字。C#中的关键字还是挺多的,参考 C# 关键字。</li>
<li><strong>驼峰命名</strong>:
<ul>
<li><strong>文件名、类名、接口、方法</strong>等都是大驼峰:<code>UserName</code>。</li>
<li><strong>局部变量</strong>为小驼峰:<code>userName</code>。</li>
<li><strong>字段</strong>:下划线+小驼峰/大驼峰都可以 <code>_userName</code>、<code>_UserName</code>,或者"<code>m_</code>"开头,按照团队规范即可。</li>
<li><strong>常量</strong>:全大写(下划线分割),或者大驼峰都可以,<code>USER_NAME</code>、<code>UserName</code>。</li>
</ul>
</li>
</ul>
<pre><code class="language-csharp">public string UserName { get => _UserName; set => UserName = value; }
public string _UserName;
public const int Max=100;
public static int MaxAge =100;
private static int _MinAge = 20;
public void Sum(int a, int b)
{
int sum = a + b;
}
</code></pre>
<h2 id="section-4">2.3、注释://</h2>
<ul>
<li><strong>单行注释</strong>:<code>//</code>开头。</li>
<li><strong>多行注释</strong>:<code>/*</code>多行注释 <code>*/</code>(同css)</li>
<li><strong>XML注释</strong>:<code>///</code>用于类型定义、方法、属性、字段等成员的XML注释,参考:《C#文档XML注释》</li>
</ul>
<pre><code class="language-csharp">/// <summary>
/// XML注释,计算和
/// </summary>
public void Sum(int a, int b)
{
//单行注释
int sum = a + b;
/*
多行注释
输出结果
*/
Console.WriteLine(sum);
}
</code></pre>
<h2 id="section-5">2.4、作用域</h2>
<p>变量的作用域就是指变量的有效范围,C#中的作用域可以简单理解为 <strong>花括号<code>{ 代码块 }</code></strong> 的范围,可以是类、方法、控制逻辑(for、while等),或者就一个单纯的<code>{}</code>。</p>
<ul>
<li>一个花括号 <code>{}</code>内代码为一个独立的代码区域,有独立的作用域,变量在该作用域内有效。</li>
<li>花括号 <code>{}</code>作用域可以多级嵌套,比如类中包含方法,方法内包括控制逻辑,子作用域可以访问父级的变量(字段、属性、方法、具备变量)。简单理解就是:<strong>子级可以访问父级的成员</strong>。</li>
</ul>
<pre><code class="language-csharp">private int x = 1; //类字段
void Main()
{
var y = 1 + x; //私有变量
if (y > 0)
{
int z = x + y + 1; //可以访问父级成员
Console.WriteLine(z);
{
int w = x+y+z+1;//可以访问父级成员,及父级的父级
Console.WriteLine(w);
}
}
}
</code></pre>
<blockquote>
<p>📢一般情况下,变量的作用域是由代码的词法环境(就是编写代码的位置)来决定的,这比较容易理解。例外情况就是C#中的闭包,常见于动态函数、委托。</p>
</blockquote>
<hr>
<h1 id="section-6">03、申明语句</h1>
<table>
<thead>
<tr>
<th><strong>申明变量</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>Type v</td>
<td>申明指定类型的变量,<code>int x</code>,<code>List<int> list</code></td>
</tr>
<tr>
<td>var</td>
<td>隐式匿名类型<code>var</code>,用<code>var</code>申明变量,编译器根据值推断出类型变量,因此要求必须赋初始值。</td>
</tr>
<tr>
<td>const</td>
<td>申明一个常量,申明时必须赋初始值,且不可修改</td>
</tr>
<tr>
<td>ref</td>
<td>reference 变量是引用另一个变量(称为引用)的变量,可以看做是其别名(分身)</td>
</tr>
</tbody>
</table>
<pre><code class="language-csharp">void Main()
{
int x =100;
List<int> list = new List<int>();
List<int> list2 = new();//前面已知了类型,后面可省略
int[] arr = ; //C#12的集合表达式,方便的创建数组、集合
List<int> arr2 = ;
var n = 1;//匿名类型,自动推断类型
var list3 = new List<int>;
ref int n2 = ref n; //ref另一个变量的别名,n2、n实际指向同一个值,是等效的
const int max =100; //常量
var (name,age) = ("sam",18); //多个变量一起申明、赋值,这只是一种简化的语法糖
(x, n) = (n, x); //还可以用该语法交换变量值,非常优雅
}
</code></pre>
<h2 id="const">3.1、const常量</h2>
<p>const 常量,顾名思义就是值永远不会改变的“变量”,可用于局部<strong>变量、字段</strong>。比如<code>Math.PI</code>,<code>Int.MaxValue</code>,用于一些已知的、不会改变的值。</p>
<ul>
<li>申明常量的同时必须赋初始化值,不可修改,在编译时值会内联到代码中。</li>
<li>常量只能用于C#内置的值类型、枚举,及字符串。</li>
<li>常量值支持表达式,不过仅限于简单的运算,要能在编译时计算出确定的值。</li>
<li>枚举其实也是常量。</li>
<li>当用定义<code>const</code> 字段时,该常量字段就和静态字段一样,属于类本身,直接使用。</li>
</ul>
<pre><code class="language-csharp">const double r = 5.0;
const double rs = 2 * Pi * r;
</code></pre>
<blockquote>
<p>📢 要注意常量(包括枚举)在编译时是把值内联到IL代码中的,因此如果跨程序集引用时,必须一起更新,否则就会出Bug。</p>
</blockquote>
<p><img src="https://img2024.cnblogs.com/blog/151257/202405/151257-20240502170239196-587517827.png" alt="image.png"></p>
<h2 id="ref">3.2、ref 引用(别名/分身)</h2>
<p>ref 关键字的核心点就是引用另一个变量的地址,可看做是其别名(分身),指向同一地址。作用和指针操作比较相似,<code>int* y = &x;</code>,不过<code>ref</code>更安全、更方便。<br>
具体在使用上有以下一些场景:</p>
<table>
<thead>
<tr>
<th><strong>使用场景</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>引用传递参数</td>
<td>方法调用时传递引用参数,方法内可修改参数值 ,<code>Foo(ref int number)</code></td>
</tr>
<tr>
<td>ref return</td>
<td>返回一个<code>ref</code>变量,<code>public ref int Foo(ref int n){return ref n;}</code></td>
</tr>
<tr>
<td>ref 变量</td>
<td>引用另一个局部变量,<code>ref int y = ref x</code></td>
</tr>
<tr>
<td>ref 条件表达式</td>
<td><code>ref</code> 用在三元表达式<code>条件? ref (true):ref (fasle)</code>中,返回引用</td>
</tr>
<tr>
<td>ref struct</td>
<td>让<code>struct</code>完全分配在栈上、不能装箱,只能用于局部变量、参数,一些高性能的场景</td>
</tr>
</tbody>
</table>
<pre><code class="language-csharp">int x = 1;
ref int y = ref x;//x、y其实同一个变量
Console.WriteLine($"{x},{y}"); //1,1
x++;
Console.WriteLine($"{x},{y}"); //2,2
y++;
Console.WriteLine($"{x},{y}"); //3,3
//换个数组
int[] arr = new int[] { 0, 1, 2};
ref int a = ref arr;
a=100;
Console.WriteLine(arr); //100 1 2
</code></pre>
<ul>
<li><code>ref readonly</code> :所指向的变量不能修改值,但可以用<code>ref</code>重新分配一个<code>reference</code> 变量。</li>
<li><code>ref</code>返回值:用于一个方法的返回值,返回一个变量的引用(别名)</li>
</ul>
<pre><code class="language-csharp">void Main()
{
var arr = new int[] { 1, 2, 3 };
ref int f = ref GetFirst(arr);
f = 100;
Console.WriteLine(arr); //100 2 3
}
private ref int GetFirst(int[] arr)
{
returnref arr;
}
</code></pre>
<blockquote>
<p>🔊 在某些场景使用<code>ref</code>可以避免值类型在传递时的拷贝操作,从而提高性能,不过不同场景不同,需要具体分析、经过性能测试再确定。</p>
</blockquote>
<hr>
<h1 id="section-7">04、常用(控制)语句</h1>
<table>
<thead>
<tr>
<th><strong>语句</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>if</code></td>
<td>条件语句,<code>if(true){ 执行 }</code></td>
</tr>
<tr>
<td><code>if</code>...<code>else</code></td>
<td>条件语句,<code>if(true){} else(){}</code></td>
</tr>
<tr>
<td><code>if</code>...<code>else if</code>...<code>else</code></td>
<td>同上,中间可以接多个<code>else if</code>,不过这个时候一般建议重构下,比如用你<code>switch</code>模式匹配</td>
</tr>
<tr>
<td><code>switch</code>...<code>case</code></td>
<td>根据条件处理多个分支:<code>switch</code>(条件){ <code>case</code> }。<code>case</code>命中后,注意<code>break</code>结束,否则会继续执行</td>
</tr>
<tr>
<td><code>while</code>(true)</td>
<td>循环:条件为true就会循环执行</td>
</tr>
<tr>
<td><code>do</code><code>while</code>(true)</td>
<td>循环:先执行后判断条件</td>
</tr>
<tr>
<td><code>for</code>循环</td>
<td>循环:<code>for</code>条件循环,支持多个语句逗号隔开。<code>for(int i =0; i<max; i++)</code></td>
</tr>
<tr>
<td><code>foreach in</code></td>
<td>循环元素:<code>foreeach(int item in items)</code>,实现了IEnumerable,或有无参数 GetEnumerator()</td>
</tr>
<tr>
<td><code>await foreach</code></td>
<td><code>foreach</code>的 异步版本</td>
</tr>
<tr>
<td>List.<code>ForEach</code>()</td>
<td><code>List<T></code>自带的循环执行方法,<code>list.ForEach(s=> s.Dump());</code></td>
</tr>
<tr>
<td><code>break</code></td>
<td>跳出循环语句,for、foreach、while、switch、do。跳出<strong>最近的</strong>语句块,如果多层嵌套只会对最近的有效</td>
</tr>
<tr>
<td><code>continue</code></td>
<td>继续下一次循环,只是后面的代码不执行了,应用条件同<code>break</code></td>
</tr>
<tr>
<td><code>return</code></td>
<td>结束<strong>方法/函数</strong>并返回结果(若有),注意是针对函数的。</td>
</tr>
<tr>
<td><code>goto</code></td>
<td>跳转语句到指定标签,单独标签或者<code>case</code>值,一般不建议使用,<code>goto</code>可读性不太好</td>
</tr>
<tr>
<td><code>throw</code></td>
<td>抛出异常,不再执行后面的代码</td>
</tr>
<tr>
<td><code>try.catch.finally</code></td>
<td>异常处理,<code>throw</code> 抛出一个异常</td>
</tr>
<tr>
<td>checked、unchecked</td>
<td>对整数运算语句进行溢出检查、不检查,如果检查溢出会抛出OverflowException</td>
</tr>
<tr>
<td>fixed</td>
<td>申明指针固定一个可移动(回收)变量,防止被GC回收,在<code>unsafe</code>代码中运行</td>
</tr>
<tr>
<td>stackalloc</td>
<td>在堆栈上分配内存,<code>int* ptr = stackalloc int</code></td>
</tr>
<tr>
<td>lock</td>
<td>互斥锁 Monitor 的语法糖,保障同时只有一个线程访问共享资源 <code>lock(obj){ }</code></td>
</tr>
<tr>
<td>using</td>
<td>引用命名空间,释放IDisposable ,</td>
</tr>
<tr>
<td>yield</td>
<td>用于迭代器中返回一个迭代值<code>yield return value</code>,或表示迭代结束<code>yield break</code>。</td>
</tr>
</tbody>
</table>
<blockquote>
<p>📢 switch 在C#8以上的更多特性,参考后文《C#的模式匹配》</p>
</blockquote>
<h2 id="try-catch">4.1、try-catch异常处理</h2>
<p>一个标准的异常处理流程:</p>
<ul>
<li><strong>try</strong>:功能代码,需要捕获异常的地方。</li>
<li><strong>catch</strong>:捕获异常,处理异常。支持多个<code>catch</code>语句,捕获不同的异常,多个<code>catch</code>按照顺序执行。<code>catch</code>后面可以用<code>when</code>表达式添加更多筛选条件。</li>
<li><strong>finally</strong>:最后执行的代码,无论是否有异常发生都会执行,多用于最后的清理工作。</li>
<li><strong>throw</strong>:可以抛出一个新的异常,也可以在<code>catch</code>直接<code>throw;</code>,保留原始堆栈信息。</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/151257/202405/151257-20240502202840981-859709339.png" alt="image"></p>
<pre><code class="language-javascript"> try
{
//功能代码
throw new ArgumentException("参数name为null");
}
//用when添加更详细的筛选条件
catch (ArgumentException e) when (e.InnerException ==null)
{
//处理异常,如记录日志
}
catch (Exception e)
{
//处理异常
throw; //直接throw,保留原始堆栈信息
}
finally
{
//最后执行的代码,无论是否有异常发生都会执行,多用于最后的清理工作
}
</code></pre>
<blockquote>
<p>📢异步(线程)中的异常一般不会抛出到调用线程(或主线程),只会在<code>await</code>,或获取<code>Task.Result</code>时才会被抛出来,更多可查看异步编程相关章节。</p>
</blockquote>
<h2 id="using-5">4.2、using 的5种用法</h2>
<p>using 在C#中有很多中用途,常用来引用命名空间、简化释放资源。</p>
<table>
<thead>
<tr>
<th><strong>using 用途</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>using namespace</td>
<td>引用命名空间,比较常用,基本每个类都会使用。</td>
</tr>
<tr>
<td>global using</td>
<td>项目全局引用,避免每个类都重复<code>using</code> 相同的命名空间。</td>
</tr>
<tr>
<td>using 别名</td>
<td>用<code>using</code>来创建命名空间或类型的别名,简化代码中的使用。</td>
</tr>
<tr>
<td>using static</td>
<td>引入一个类型的静态成员、嵌套类型,代码中直接使用引入的静态成员。</td>
</tr>
<tr>
<td>using 语句</td>
<td>using 语句可确保正确使用 IDisposable 实例,<code>using(var r){}</code>,简化后无需括号</td>
</tr>
</tbody>
</table>
<blockquote>
<p>📢 命名空间 namespace 用于组织代码(作用域)的主要方式,用关键字<code>namespace</code>来命名,可嵌套。C#10 中可以用文件范围命名空间,减少一层括号嵌套。</p>
</blockquote>
<ul>
<li><code>global using</code>的最佳实现是一般创建一个公共的类文件“<code>Usings.cs</code>”,专门放置项目中全局的公共<code>using</code>。</li>
<li>用<code>using</code>来创建命名空间别名,使用时需要用到操作符<code>::</code>来访问下级。</li>
<li><code>using</code>可创建任意类型的别名,包括数组、泛型、元祖、指针。</li>
</ul>
<pre><code class="language-csharp">global using System.Text; //全局引用命名空间
using System.Text; //引用命名空间
using json = System.Text.Json.JsonSerializer;//类型别名
using NumberList = double[]; //类型别名:数组
using Point = (int X, int Y); //类型别名:元祖ValueTuple<int, int>
using jsons = System.Text.Json;//空间别名
//namespace myspace; 效果同下,简化写法,可节省一对大括号
namespace myspace
{
public class Program
{
void Main()
{
json.Serialize(new Object());
jsons::JsonSerializer.Serialize(new Object()); //这用到操作符::
NumberList arr = ;
}
}
}
</code></pre>
<blockquote>
<p>📢 从<code>.Net</code>6开始,C#项目会根据项目类型隐式包含一些<code>using</code>引用,比如<code>System</code>、<code>System.Text</code>。</p>
</blockquote>
<ul>
<li><code>using static</code>,引入一个类型的静态成员、嵌套类型,代码中直接使用引入的静态方法。</li>
</ul>
<pre><code class="language-csharp">using static System.Math;
voidMain()
{
var a = Abs(-2 * PI ); //直接使用Math下的静态成员
}
</code></pre>
<p><strong>🔸using</strong> 语句确保对象在<code>using</code>语句结束时被释放(调用<code>Dispose</code>)。也可以直接用<code>using</code>申明变量,不用大括号<code>{}</code>,这是一种简化的写法,会在作用域(方法、语句块)结束时释放。</p>
<pre><code class="language-csharp">using (StreamReader reader = File.OpenText("numbers.txt"))
{
Console.WriteLine("do read...");
}
// 简化写法,效果和上面一样,直接用using修饰 变量申明
using StreamReader reader2 = File.OpenText("numbers.txt");
//编译后的代码:
StreamReader reader = File.OpenText ("numbers.txt");
try
{
Console.WriteLine ("do read...");
}
finally
{
if (reader != null)
{
((IDisposable)reader).Dispose ();
}
}
</code></pre>
<blockquote>
<p>📢 <code>using</code>语句是一种语法糖,会自动生成<code>try...finally</code>代码。</p>
</blockquote>
<hr>
<h1 id="section-8">参考资料</h1>
<ul>
<li>C#DotNet资料导航</li>
<li>C#.NET体系图文概述—2024总结</li>
<li>C# 语言文档</li>
<li>《C#8.0 In a Nutshell》</li>
</ul>
<hr>
<blockquote>
<p><strong>©️版权申明</strong>:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!<em>原文编辑地址-语雀</em></p>
</blockquote><br><br>
来源:https://www.cnblogs.com/anding/p/18170347
頁:
[1]