语法经验
约 2595 字大约 9 分钟
2025-12-04
本篇的语法经验并非基础语法介绍,而是笔者个人对部分语法的使用经验以及使用时遇到的坑,希望能够在开发上帮助他人不踩相同的坑。愿世上再无Error
如果您对基础语法不太了解或根本没有概念的话,可以先阅读
基础介绍进行初步了解。在零基础的情况下 菜鸟教程 、 C语言中文网 都是最好的引路人,基础不免有些枯燥,但是在看懂程序的那一刻相信都是有成就感的。
1.1 属性和字段
属性 和 字段 作为两个比较相似的概念,在笔者初学时一直有所混淆。~~不会有人用半天都搞不清吧?原来是我啊哈哈~~
首先,在 属性 和 字段 之前,要先知道变量是什么。变量 是一个特定的存储单元,它拥有着自定义的变量名称,可设置的变量值。
public string test = "test string"如上便是一个变量,public 是变量的适用范围的声明,string 是变量类型,test 是变量名,"test string" 是变量值。
而这个变量也可以被称作字段,是一个 公共字段,那字段和属性的区别是什么呢?按照笔者的理解简单来讲,属性 是一个可以在调用时,进行具体数据处理的变量,字段 是一个只可以改变值的变量。
举个例子,下面是同一个含义的 字段 和 属性 ,都代表的是一个存放电话号码的方式。
字段:
public string phone = "+86 123456789000"属性:
private string? _phone;
public string? phone
{
// 可以省略,让属性作为只写
get
{
if (_phone == null)
return null;
return _phone + "+86";
}
// 可以省略,让属性作为只读
set
{
_phone = value;
}
}这么一看 属性 要多写很多代码,但实际上在使用时,可以通过传入的字符串自动给电话号码添加前缀。而 get 中的处理完全可以根据需求进行编写,使用上来说,相比单一的 字段 赋值,属性 可以有更多的数据处理,可以让变量的处理更加统一。同样的 set 也可以自行数据处理。
一般来说 属性 不会单独出现,而是存在于实体类中,如下的用户实体类
public class UserInfo
{
public string username {get; set;}
private string? _phone;
public string? phone
{
get
{
if (_phone == null)
return null;
return _phone + "+86";
}
set
{
_phone = value;
}
}
public string nickname {get; set;}
}通过 username、phone、nickname 描述了 UserInfo 的属性,在使用时可以对每个 属性 赋值进行使用。区别于 字段 的赋值,实体类中的 属性 可以进行单独修改更加多样化。
就笔者个人使用体验而言,对于有着固定结构的数据,属性 可以非常简便的实现数据结构的复用。而 字段 一般会用于比较单一的变量,虽然字典类型也可以处理复杂的变量,但笔者个人认为带有 属性 的实体类可以更好的统一管理和扩展。
1.2 反射
反射 (Reflection) ,官方解释是命名空间中的 System.Reflection 类以及 System.Type 可用于获取有关加载的程序集及其中定义的类型的信息,例如类、接口和值类型(即结构和枚举)。 还可以使用 反射 在运行时创建类型实例,以及调用和访问它们。
笔者个人经验而言,可以简单的理解为 反射 可以认为是在程序中调用外部文件以及文件内的程序。非常典型的来说,现有dll文件,如果需要使用内部的程序、方法或函数,那便可以使用反射来调用(虽然还是不怎么用)。
是否会觉得用 反射 调用dll的方法有些多此一举,明明可以直接用添加项目引用功能直接添加dll文件,编写反射反而还变得麻烦了。其实 反射 的作用是为了动态调用,只需要将 反射 进行初步封装,就可以根据传入的参数调用指定的dll文件和方法。
类库DLL:
// dll内程序
namespace TestClassLibrary
{
public class TestClass
{
public static void test(string input)
{
Console.WriteLine(input);
}
}
}项目调用:
using System.Reflection;
namespace TestApp
{
internal class Program
{
static void Main(string[] args)
{
string DllPath = @"your_dll_path";
// 加载dll文件
Assembly asm = Assembly.LoadFrom(DllPath);
// 获取类名,必须使用 命名空间+类名
Type t = asm.GetType("TestClassLibrary.TestClass");
// 实例化类
object o = Activator.CreateInstance(t);
// 获取指定方法
MethodInfo method = t.GetMethod("test");
// 入参参数
object[] obj =
{
"Hello world!"
};
// 对方法进行调用
var keyData = method.Invoke(o, obj);
}
}
}上述是一个简易的反射调用例子,在运行 TestApp 项目后,就会弹出控制台打印出 "Hello world!",这样就可以简易地调用其他的dll文件,稍作封装就可以实现动态调用dll库了。虽然动态调用非常便利,但由于性能问题,笔者在此不建议频繁使用拖垮整个程序的运行效率。
1.3 委托和事件
1.3.1 委托
委托 (Delegate),是 C# 中比较特殊的语法,在各种地方都很常用。笼统点讲,委托 是一种类型安全的函数指针,它允许将方法作为参数传递给其他方法。如果需要详细了解可以查阅 微软官方委托简介。
委托 是一个统称,具体上来说分为一下四类 可以在自己的环境中调试运行熟悉一下:
- delegate 普通委托,可以无返回值也可以指定返回值类型
// 定义委托
public delegate int TestDelegate(int x, int y);
// 定义被调用方法
public static int Add(int x, int y)
{
return x + y;
}
static void Main(string[] args)
{
// 实例化委托
TestDelegate test_delegate = new TestDelegate(Add);
// 传入的入参调用Add方法后返回
Console.WriteLine(test_delegate(4, 6));
}- action 无返回的泛型委托
// 定义被调用方法
public static void OutputString(string input)
{
input = "input: "+ input;
Console.WriteLine(input);
}
// 将方法作为参数传递
public static void TestAction<T>(Action<T> action, T obj)
{
action(obj);
}
static void Main(string[] args)
{
// 设定泛型为string
TestAction<string>(OutputString, "Hello World");
}- func 必须有返回值的泛型委托
// 定义被调用方法
private static int Add(int a, int b)
{
return a + b;
}
// 将方法作为参数
public static int TestFunc<T1, T2>(Func<T1, T2, int> func, T1 a, T2 b)
{
return func(a, b);
}
static void Main(string[] args)
{
int result = TestFunc<int, int>(Add, 4, 6);
Console.WriteLine("result=" + result);
}- predicate 只接受一个入参且返回bool的委托
// 定义被调用的方法
private static bool CompareXY(Point obj)
{
if (obj.X > obj.Y)
return true;
else
return false;
}
static void Main(string[] args)
{
Point[] points = {
new Point(1,2),
new Point(2,1),
new Point(1,1),
new Point(2,2)
};
// 将CompareXY作为参数传入
// Array.Find()的第二个参数为predicate
Point first = Array.Find(points, CompareXY);
}简单来说,上面四个都是将 方法 作为 入参 传入委托进行调用,不同的是对 传入方法 的要求。可以通过传入相同结构的不同方法,实现更加复用性的代码,举个例子,如果需要对两个数字进行运算操作,只需要定义好对应的被调用方法,在需要的地方进行委托调用即可动态实现运算。而独立出来的被调用方法就可以统一进行扩展管理。
1.3.2 事件
事件(Event),笼统来说是用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。
对笔者而言,最为熟悉的 事件 就是在 winform 项目中的,winform 就是windows窗体程序(不清楚的可以自行查阅一下,理解成正常使用的桌面程序即可)。在 winform 中时常会用到鼠标点下时触发事件、键盘按下时触发事件等。除去 winform 项目,事件也可用于其他项目。如果需要详细了解可以查阅 微软官方事件简介。
此处代码引用 菜鸟教程 的实例代码:
// 定义一个委托类型,用于事件处理程序
public delegate void NotifyEventHandler(object sender, EventArgs e);
// 发布者类
public class ProcessBusinessLogic
{
// 声明事件
public event NotifyEventHandler ProcessCompleted;
// 触发事件的方法
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}
// 模拟业务逻辑过程并触发事件
public void StartProcess()
{
Console.WriteLine("Process Started!");
// 这里可以加入实际的业务逻辑
// 业务逻辑完成,触发事件
OnProcessCompleted(EventArgs.Empty);
}
}
// 订阅者类
public class EventSubscriber
{
public void Subscribe(ProcessBusinessLogic process)
{
// 注册实际调用的事件方法
process.ProcessCompleted += Process_ProcessCompleted;
}
// 被调用方法
private void Process_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}
internal class Program
{
static void Main(string[] args)
{
ProcessBusinessLogic process = new ProcessBusinessLogic();
EventSubscriber subscriber = new EventSubscriber();
// 订阅事件
subscriber.Subscribe(process);
// 启动过程
process.StartProcess();
Console.ReadLine();
}
}上述示例简单实现了一个简单的发布和订阅,上述的注释已经比较完善可以先自行理解一下。发布者类 中的声明事件,就是通过实例化一个委托用于接受实际需要调用的方法。在 订阅者类 中通过给委托注册实际事件,在 OnProcessCompleted() 时就会自动触发。
还是用 winform 项目的逻辑进行解释,在属性中设置的鼠标点下时触发事件就像是提前定义的 委托,再给该 事件 添加方法时,就相当于是注册了对应的调用方法,在实际触发鼠标点下的事件时就会自动调用指定方法。
1.3.3 委托和事件的区别
这个问题几乎是八股文里的常客,在刚刚了解了 委托 和 事件 后,是否对这两个语法有了一定的了解。在笔者的经验里 事件 是 委托 的封装应用,通过对 委托 的封装提高了安全性,同时提供了发布-订阅的模式。使用上也能感受到,每次注册事件其实就是对委托进行方法的传入,和使用普通委托区别并不大。