05. C#入门系列【类、结构、枚举】:从青铜到王者的进阶之路

C#入门系列【类、结构、枚举】:从青铜到王者的进阶之路

一、引言:为什么需要自定义类型?

在C#的世界里,系统自带的类型(如intstringbool)就像是基础武器,能解决一些简单问题。但当你面对复杂的业务场景时,就需要像英雄联盟里的英雄一样,打造属于自己的"终极武器"------自定义类型。

比如,你要开发一个游戏,需要描述一个角色:

  • 只用系统类型:

    csharp 复制代码
    string name = "盖伦";
    int level = 18;
    double health = 616.28;
    string[] skills = { "致命打击", "勇气", "审判", "德玛西亚正义" };
  • 用自定义类型:

    csharp 复制代码
    var garen = new Hero { 
        Name = "盖伦", 
        Level = 18, 
        Health = 616.28,
        Skills = new[] { "致命打击", "勇气", "审判", "德玛西亚正义" } 
    };

哪个更简洁明了?一目了然!

二、自定义类型的"三剑客"

1. 类(Class):最全能的战士

类就像是游戏中的全能型英雄,既能扛伤害(封装数据),又能打输出(提供方法)。

csharp 复制代码
public class Hero
{
    // 字段:英雄的属性
    private string _name;
    
    // 属性:英雄的公开特性
    public string Name 
    { 
        get => _name; 
        set => _name = value ?? throw new ArgumentNullException("名字不能为空"); 
    }
    
    public int Level { get; set; } = 1; // 默认1级
    public double Health { get; set; }
    public string[] Skills { get; set; }
    
    // 方法:英雄的技能
    public void Attack() => Console.WriteLine($"{Name}使用了普通攻击!");
    
    public void UseSkill(int skillIndex)
    {
        if (skillIndex < 0 || skillIndex >= Skills?.Length)
        {
            Console.WriteLine($"{Name}没有这个技能!");
            return;
        }
        Console.WriteLine($"{Name}使用了技能:{Skills[skillIndex]}!");
    }
}

使用示例

csharp 复制代码
var ezreal = new Hero
{
    Name = "探险家",
    Level = 15,
    Health = 520.36,
    Skills = new[] { "秘术射击", "精华跃动", "奥术跃迁", "精准弹幕" }
};

ezreal.UseSkill(3); // 输出:探险家使用了技能:精准弹幕!

2. 结构(Struct):轻量级特种兵

结构就像是游戏中的特种兵,身材小巧(值类型),但行动迅速(无需堆内存分配)。

csharp 复制代码
public struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
    
    // 构造函数
    public Point(double x, double y) => (X, Y) = (x, y);
    
    // 方法:计算到原点的距离
    public double DistanceToOrigin() => Math.Sqrt(X * X + Y * Y);
    
    // 重写ToString方法
    public override string ToString() => $"({X}, {Y})";
}

使用示例

csharp 复制代码
var point = new Point(3, 4);
Console.WriteLine($"点{point}到原点的距离是:{point.DistanceToOrigin()}"); 
// 输出:点(3, 4)到原点的距离是:5

类 vs 结构

特性 类(引用类型) 结构(值类型)
存储位置 堆(Heap) 栈(Stack)或字段
拷贝方式 引用拷贝 值拷贝
默认初始值 null 各字段的默认值
适合场景 复杂对象、需要继承 轻量级数据、频繁创建销毁

3. 枚举(Enum):游戏中的技能栏

枚举就像是游戏中的技能栏,把一组相关的值放在一起,方便选择和使用。

csharp 复制代码
public enum HeroType
{
    Warrior,    // 战士
    Mage,       // 法师
    Assassin,   // 刺客
    Tank,       // 坦克
    Support,    // 辅助
    Marksman    // 射手
}

// 为Hero类添加Type属性
public class Hero
{
    // 其他成员保持不变...
    public HeroType Type { get; set; }
}

使用示例

csharp 复制代码
var yasuo = new Hero
{
    Name = "亚索",
    Type = HeroType.Assassin,
    Skills = new[] { "斩钢闪", "风之障壁", "踏前斩", "狂风绝息斩" }
};

Console.WriteLine($"{yasuo.Name}是一名{yasuo.Type}型英雄。"); 
// 输出:亚索是一名Assassin型英雄。

// 更友好的输出
string GetTypeName(HeroType type) => type switch
{
    HeroType.Warrior => "战士",
    HeroType.Mage => "法师",
    HeroType.Assassin => "刺客",
    HeroType.Tank => "坦克",
    HeroType.Support => "辅助",
    HeroType.Marksman => "射手",
    _ => type.ToString()
};

Console.WriteLine($"{yasuo.Name}是一名{GetTypeName(yasuo.Type)}型英雄。"); 
// 输出:亚索是一名刺客型英雄。

三、进阶技巧:让自定义类型更强大

1. 继承与多态:英雄的"觉醒"

通过继承,你可以让英雄获得更强大的能力。

csharp 复制代码
// 基类:所有英雄的共同特性
public abstract class BaseHero
{
    public string Name { get; set; }
    public int Level { get; set; } = 1;
    
    // 抽象方法:必须由子类实现
    public abstract void UltimateSkill();
    
    // 虚方法:子类可以重写
    public virtual void Attack() => Console.WriteLine($"{Name}进行了普通攻击。");
}

// 子类:战士英雄
public class WarriorHero : BaseHero
{
    public override void UltimateSkill() => Console.WriteLine($"{Name}使用了终极技能:神罗天征!");
    
    public override void Attack() => Console.WriteLine($"{Name}使用了战技:突刺!");
}

// 子类:法师英雄
public class MageHero : BaseHero
{
    public override void UltimateSkill() => Console.WriteLine($"{Name}使用了终极技能:陨石天降!");
    
    public override void Attack() => Console.WriteLine($"{Name}使用了魔法:火球术!");
}

多态演示

csharp 复制代码
BaseHero[] heroes = new BaseHero[]
{
    new WarriorHero { Name = "赵云" },
    new MageHero { Name = "诸葛亮" }
};

foreach (var hero in heroes)
{
    hero.Attack();
    hero.UltimateSkill();
    Console.WriteLine("----------------");
}

// 输出:
// 赵云使用了战技:突刺!
// 赵云使用了终极技能:神罗天征!
// ----------------
// 诸葛亮使用了魔法:火球术!
// 诸葛亮使用了终极技能:陨石天降!
// ----------------

2. 接口:英雄的"装备"

接口就像是游戏中的装备,只要英雄"穿上"(实现),就能获得相应的能力。

csharp 复制代码
// 可远程攻击接口
public interface IRemoteAttack
{
    void RemoteAttack();
}

// 可控制接口
public interface IControl
{
    void ControlEnemy();
}

// 射手英雄:实现多个接口
public class MarksmanHero : BaseHero, IRemoteAttack, IControl
{
    public override void UltimateSkill() => Console.WriteLine($"{Name}使用了终极技能:万箭齐发!");
    
    public void RemoteAttack() => Console.WriteLine($"{Name}进行了远程攻击!");
    
    public void ControlEnemy() => Console.WriteLine($"{Name}使用了控制技能:致盲!");
}

接口使用

csharp 复制代码
var jinx = new MarksmanHero { Name = "金克丝" };
jinx.RemoteAttack();  // 金克丝进行了远程攻击!
jinx.ControlEnemy();  // 金克丝使用了控制技能:致盲!

3. 泛型:英雄的"万能钥匙"

泛型就像是游戏中的万能钥匙,可以适配各种场景。

csharp 复制代码
// 装备槽:可以存放任何类型的装备
public class EquipmentSlot<T>
{
    private T _equipment;
    
    public void Equip(T equipment)
    {
        Console.WriteLine($"装备了:{equipment}");
        _equipment = equipment;
    }
    
    public T Unequip()
    {
        Console.WriteLine($"卸下了:{_equipment}");
        var temp = _equipment;
        _equipment = default;
        return temp;
    }
}

// 使用示例
var weaponSlot = new EquipmentSlot<string>();
weaponSlot.Equip("无尽之刃");  // 装备了:无尽之刃
var weapon = weaponSlot.Unequip();  // 卸下了:无尽之刃

四、常见陷阱与避坑指南

1. 结构的"甜蜜陷阱"

结构是值类型,频繁装箱拆箱会影响性能。

错误示范

csharp 复制代码
var points = new List<Point>();
for (int i = 0; i < 1000000; i++)
{
    points.Add(new Point(i, i));  // 大量结构实例,可能导致性能问题
}

解决方案

  • 对于大数据量,优先使用类
  • 使用Span<T>Memory<T>避免装箱

2. 枚举的"数字游戏"

枚举默认是int类型,可能导致意外赋值。

错误示范

csharp 复制代码
HeroType type = (HeroType)99;  // 无效的枚举值,但编译通过

解决方案

  • 使用[Flags]特性创建位标志枚举

  • 在使用前验证枚举值有效性

    csharp 复制代码
    if (!Enum.IsDefined(typeof(HeroType), type))
    {
        Console.WriteLine("无效的英雄类型!");
    }

3. 继承的"深渊巨口"

过度继承会导致代码复杂度爆炸,就像游戏中技能点加错了一样。

错误示范

csharp 复制代码
public class TankWarriorMageHero : BaseHero  // 多重职责,违反单一职责原则
{
    // ...
}

解决方案

  • 优先使用组合而非继承
  • 遵循SOLID设计原则
  • 使用接口实现多角色能力

五、总结:自定义类型的"通关秘籍"

  1. 选择合适的类型

    • 类:复杂对象,需要继承和多态
    • 结构:轻量级数据,频繁创建销毁
    • 枚举:固定值集合,提高代码可读性
  2. 善用高级特性

    • 继承:扩展功能,实现多态
    • 接口:定义契约,实现横向扩展
    • 泛型:提高代码复用性
  3. 避开常见陷阱

    • 注意结构的性能问题
    • 验证枚举值的有效性
    • 避免过度继承

掌握了这些,你就能在C#的世界里像职业选手一样,灵活运用各种自定义类型,轻松应对各种复杂的业务场景,成为代码界的"王者"!

相关推荐
isyangli_blog7 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008118 小时前
FastAPI APIRouter
开发语言·python
Benszen8 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆8 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木8 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充8 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~8 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6169 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
春生野草9 小时前
反射、Tomcat执行
java·开发语言
雪的季节10 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt