3、C#基础回顾:变量与数据类型、流程控制、面向对象基础、委托与事件

各位同学,咱们直接进入正题。做IPC运动控制,说白了就是用代码指挥机器动起来。C#是我们的指挥语言,基础不牢,后面写控制逻辑时你会很痛苦。我见过太多人,上来就写运动控制代码,结果连变量类型都搞混,导致电机跑飞了还找不到原因。

这一章,我带大家快速过一遍C#的核心基础。别嫌啰嗦,这些都是我踩过的坑换来的经验。

3.1 变量与数据类型——选对类型,少出Bug

变量就是存数据的盒子。但盒子有大有小,装的东西也不一样。选错了,轻则浪费内存,重则数据溢出,电机直接给你来个急停。

值类型 vs 引用类型

这是C#里最基础的分水岭。值类型存的是数据本身,引用类型存的是数据的地址。我刚开始做项目时,有一次把结构体当类用,结果赋值时数据没复制过去,位置反馈一直不对。排查了一下午,才发现是类型搞混了。

类别 常见类型 存储位置 赋值行为
值类型 int, float, double, bool, struct 复制值
引用类型 string, class, array, delegate 复制引用

避坑指南:运动控制中,位置、速度这些关键数据,我建议用double类型。float精度不够,跑长距离时累积误差会让你崩溃。我曾经用float存累计位置,跑了10万脉冲后,误差达到了0.5个脉冲——这在精密定位中是不可接受的。

// 正确的数据类型选择
double targetPosition = 10000.5;  // 目标位置,单位:脉冲
double currentSpeed = 500.0;       // 当前速度,单位:脉冲/秒
bool isHomeCompleted = false;      // 回零完成标志
string axisName = "X轴";           // 轴名称,引用类型

3.2 流程控制——让机器按你的节奏走

流程控制就是告诉机器:什么情况下做什么事。运动控制里,最常用的就是条件判断和循环。

if-else 的实战用法

我写运动控制程序时,if-else用得最多。比如判断限位开关是否触发、检查是否到达目标位置。这里有个小技巧:把最可能发生的情况放在最前面,能提高执行效率。

// 实际项目中的限位判断
if (currentPosition >= positiveLimit)
{
    // 正向限位触发,立即停止
    StopAxis(axisId);
    Console.WriteLine("正向限位触发,轴已停止");
}
else if (currentPosition <= negativeLimit)
{
    // 负向限位触发
    StopAxis(axisId);
    Console.WriteLine("负向限位触发,轴已停止");
}
else
{
    // 正常范围内,继续运动
    ContinueMove(axisId, targetSpeed);
}

我的习惯:在运动控制中,尽量少用switch-case。因为轴的状态变化很快,if-else的短路特性可以更快地跳出判断。而且,if-else的可读性更好,调试时一眼就能看出逻辑。

循环——别让机器死循环

做点位运动时,经常需要循环等待某个条件满足。比如等待轴到达目标位置。但千万要注意:一定要加超时退出机制。我曾经犯过这个错,程序一直卡在while循环里,机器都撞到硬限位了还没退出。

// 带超时的等待循环
int timeout = 5000;  // 超时时间5秒
int elapsed = 0;
bool isArrived = false;

while (elapsed < timeout)
{
    if (CheckPositionReached(axisId, targetPosition))
    {
        isArrived = true;
        break;
    }
    Thread.Sleep(10);  // 每10ms检查一次
    elapsed += 10;
}

if (!isArrived)
{
    // 超时处理,紧急停止
    EmergencyStop(axisId);
    Console.WriteLine("轴运动超时,已紧急停止");
}

3.3 面向对象基础——把轴当成对象来管

面向对象编程,说白了就是把现实世界的东西抽象成代码里的对象。在运动控制里,每个轴就是一个对象。它有属性(当前位置、速度)、有方法(启动、停止)、有事件(到达位置、报警)。

封装——把轴的数据藏起来

我习惯把轴的所有参数封装成一个类。外部代码只能通过公开的方法来操作轴,不能直接修改内部数据。这样能防止误操作。

public class Axis
{
    // 私有字段,外部不能直接访问
    private double _currentPosition;
    private double _currentSpeed;
    private bool _isEnabled;

    // 公开属性,只读
    public double CurrentPosition => _currentPosition;
    public bool IsEnabled => _isEnabled;

    // 公开方法,控制轴的行为
    public void MoveTo(double targetPosition, double speed)
    {
        if (!_isEnabled)
        {
            Console.WriteLine("轴未使能,无法运动");
            return;
        }
        // 执行运动逻辑
        Console.WriteLine($"轴正在移动到 {targetPosition},速度 {speed}");
    }

    public void Enable()
    {
        _isEnabled = true;
        Console.WriteLine("轴已使能");
    }

    public void Disable()
    {
        _isEnabled = false;
        Console.WriteLine("轴已去使能");
    }
}

注意:千万不要把轴的关键参数设为public。我见过有人直接把_currentPosition设为public,结果在别的地方不小心改了它,导致位置反馈和实际位置不一致,机器直接撞机。封装不是形式,是保护。

继承——复用通用的轴逻辑

如果项目中有多种类型的轴(伺服轴、步进轴、虚拟轴),可以把公共逻辑写在基类里,子类继承并扩展。

// 基类:通用轴
public class BaseAxis
{
    public virtual void Start()
    {
        Console.WriteLine("轴启动");
    }

    public virtual void Stop()
    {
        Console.WriteLine("轴停止");
    }
}

// 子类:伺服轴
public class ServoAxis : BaseAxis
{
    public override void Start()
    {
        base.Start();  // 调用基类方法
        Console.WriteLine("伺服轴已使能");
    }

    public void SetTorqueLimit(double limit)
    {
        Console.WriteLine($"设置扭矩限制为 {limit}");
    }
}

3.4 委托与事件——让轴主动通知你

委托,你可以把它理解成一个方法的指针。事件,则是基于委托的一种发布-订阅机制。在运动控制里,这个太有用了。

为什么需要委托?

你想想看,如果轴到达目标位置后,你需要执行一系列操作:停止其他轴、记录日志、更新UI。如果把这些逻辑都写在轴类里,那轴类就太臃肿了。用委托,你可以把「到达位置后要做什么」这个行为,作为参数传进去。

// 定义委托
public delegate void PositionReachedHandler(double position);

public class AxisWithEvent
{
    // 声明事件
    public event PositionReachedHandler OnPositionReached;

    private double _currentPosition;

    public void MoveTo(double target)
    {
        // 模拟运动
        _currentPosition = target;
        Console.WriteLine($"轴到达位置 {target}");

        // 触发事件
        OnPositionReached?.Invoke(_currentPosition);
    }
}

// 使用事件
AxisWithEvent axis = new AxisWithEvent();
axis.OnPositionReached += (pos) =>
{
    Console.WriteLine($"轴已到达 {pos},执行后续操作");
    // 这里可以写停止其他轴、记录日志等逻辑
};

axis.MoveTo(1000.0);  // 运动到1000,自动触发事件

我的经验:在IPC运动控制中,事件机制是解耦的关键。轴只管运动,到达位置后通知外部。外部程序想做什么就做什么,互不干扰。我曾经用事件机制做了一个多轴联动系统,每个轴到达位置后触发事件,主控程序根据事件顺序决定下一步动作。代码清晰,调试方便。

委托的另一种用法——回调函数

有时候,运动控制卡提供的API是异步的。你发一个运动指令,它不会立即返回结果。这时候就需要回调函数。委托就是实现回调的最佳方式。

// 异步运动回调
public void AsyncMoveTo(double target, Action callback)
{
    // 模拟异步操作
    Task.Run(() =>
    {
        Thread.Sleep(1000);  // 模拟运动耗时
        bool success = true;
        callback(success);   // 运动完成后回调
    });
}

// 调用
AsyncMoveTo(5000.0, (result) =>
{
    if (result)
    {
        Console.WriteLine("异步运动完成");
    }
    else
    {
        Console.WriteLine("异步运动失败");
    }
});

好了,C#基础回顾就到这里。这些内容看起来简单,但都是运动控制编程的基石。下一章,我们会把这些知识用到实际的IPC运动控制卡编程中。到时候你会发现,基础扎实了,写控制代码就像搭积木一样顺畅。