当前位置: 首页 > news >正文

C#知识学习-015(修饰符_4)

目录

1.const

1.1 是什么​​

​​1.2 用来存什么​

1.​​3 什么时候用​​

2.readonly

2.1 核心概念

2.2 重要区别

2.2.1 ​​值类型

​​2.2.2 引用类型

2.2.3 readonly struct

​​2.2.4 readonly实例成员 (struct内的方法)​​

2.2.5 ref readonly返回值

2.2.6 in参数(方法参数上的readonly引用)

3.比较


1.const

1.1 是什么

  • 它用来声明​​常量​​。常量可以是字段(类的成员)或局部变量(方法内部的变量)。

  • ​关键点:常量一旦设定,其值就绝对不能改变!​​ 试图修改常量会导致编译错误。

​​1.2 用来存什么

  • ​基本类型:​​ 数字(intdouble等)、布尔值(true/false)。

  • ​字符串:​​ 固定文本("Hello")。

  • null:​​ 对于引用类型,只有null可以作为常量值(除了字符串)。

  • ​内插字符串常量:​​ 如果拼接的所有部分都是常量字符串,那么整个内插字符串也可以是常量

const int X = 0; // 局部常量
const string Language = "C#";
static void Main()
{const int C = 707; // 方法内部的常量Console.WriteLine($"My local constant = {C}");
}
const string FullProductName = $"Language: {Language}"; //因为Language是常量字符串,所以可以

1.​​3 什么时候用​​

  • 用来表示​​绝对不变、永恒不变​​的值。

  • ​经典例子:​

    • 数学常数:const double Pi = 3.14159;

    • 固定不变的枚举值(虽然通常用enum更好)。

    • 程序中一些永远不会变的配置值(但要非常小心)。

  • ​什么时候绝对不该用const?​
    • ​ 用来表示将来可能会变的值!​ 

    • ​ 错误例子:​

      • 软件版本号(会升级)

      • 任何来自配置文件或数据库的值(运行时才确定)

  • ​为什么不能用?​

    •  因为const的值是在​​编译时​​就确定并直接“写死”到使用它的代码里的。如果你在一个库里定义了const Version = 1;然后另一个程序引用了这个库。当你把库里的Version 改成2并重新编译库时,​​引用该库的程序必须也重新编译​​,否则它里面用的还是旧的1!因为它编译时就把1复制进去了。readonly字段没有这个问题。

2.readonly

2.1 核心概念

想象一下你有一个盒子。readonly就像是给这个盒子贴了一个标签,规定了这个盒子​​什么时候可以被放进东西​​。

这个字段只能在两个地方被赋值:

  • ​声明时初始化:​​ public readonly int MyNumber = 10;

  • ​在同一个类的构造函数中:​(对象构造完成,就不能再赋值)

    public class MyClass
    {public readonly int MyNumber;public MyClass(int number){MyNumber = number; // 允许在构造函数里赋值}public void ChangeNumber(){// MyNumber = 20; // 错误!不能在构造函数以外的地方赋值}
    }

2.2 重要区别

2.2.1 ​​值类型

(比如 intstruct):​​ 本身就直接装着数据(比如数字 10)。贴了 readonly标签后,​​就不可变了​​。

​​2.2.2 引用类型

(比如 stringList, 自定义 class):​​ 本身装的是一个​​地址​​,指向另一个地方(堆上)的真正的对象。贴了 readonly标签后:

  • ​不能把字段指向另一个对象。​

    public class MyClass
    {private readonly List<string> MyReadOnlyList = new List<string>(); // 构造时放:地址Apublic MyClass(){// 在构造函数里放是允许的(如果声明时没放)// MyReadOnlyList = new List<string>(); // 地址B (如果声明时没初始化)// 试图在构造函数里重新赋值 - 允许!因为还在构造阶段// MyReadOnlyList = new List<string>(); // 现在是地址C (覆盖了之前的A或B)}public void SomeMethod(){// 试图在构造函数之外重新赋值 - 绝对不允许!编译错误!// MyReadOnlyList = new List<string>(); // 错误 CS0191:无法对只读字段赋值}
    }
  • ​但是!​​ 你可以根据地址找到那个对象,然后​修改对象的状态​​!除非那个对象本身设计成不可变的(比如 string)。

    public class MyClass
    {// 地址指向一个空列表private readonly List<string> MyReadOnlyList = new List<string>();public void AddItem(string item){// 完全合法!我们不是换地址,我们是根据地址找到那个列表MyReadOnlyList.Add(item); // ...然后往里添加东西!}
    }

​补充:

警告:​​ 如果这个对象是公共的、可变的(比如 List),并且你通过 readonly字段暴露了它,别人就能修改它里面的东西,这可能带来安全风险(CA2104 警告)。

public class InsecureClass
{// 危险!公共只读字段指向可变对象public readonly List<string> SensitiveData = new List<string>(); 
}// 外部代码:
InsecureClass insecure = new InsecureClass();
insecure.SensitiveData.Add("Top Secret"); // 外部代码直接修改了内部数据!
  • 核心问题:违反了面向对象编程的基本原则——封装

    封装意味着一个类应该:

    • ​隐藏其内部状态(数据)的实现细节。​

    • ​只通过受控的公共接口(方法、属性)来暴露和操作这些状态。​

  • public readonly List<T> MyList这种写法直接打破了封装:

    • ​它完全暴露了内部数据结构:​​ 外部代码不仅知道你有数据,还精确地知道你用一个 List<T>来存储它。

    • ​它放弃了状态的控制权:​​ 外部代码可以绕过你设计的任何业务逻辑,直接对数据进行增删改查。

举例:

比如你有一个购物类,里面有一个 public readonly List<CartItem> Items。​购物车添加商品时,需要检查库存、计算折扣等。但是现在外部可以随意直接修改商品,就会破坏业务规则。

正确的做法是什么?​

  • ​首选:将字段设为 private(或至少 protected)。​

    private readonly List<CartItem> _items = new List<CartItem>();
  • ​通过属性或方法提供受控的访问:​​​只读视图:​​ 返回一个只读包装器 (IReadOnlyList<T>IReadOnlyCollection<T>) 或副本。
    public IReadOnlyList<CartItem> Items => _items.AsReadOnly();
    // 或者返回副本 (如果集合不大且频繁访问不是问题)
    // public List<CartItem> Items => new List<CartItem>(_items);
  • 操作方法:​​ 提供 AddItemRemoveItemClearCart等方法。在这些方法内部实现业务逻辑、验证、通知、线程同步等。
    public void AddItem(CartItem item)
    {// 检查库存...// 应用折扣规则...
    }

2.2.3 readonly struct

  • 规则:​​ 规定​struct实例一旦建好,里面的所有东西都不能再改变。​

  • 它强制要求:

    • 所有字段都必须是 readonly

    • 所有方法(除了构造函数)都不能修改结构的状态(编译器会检查)。

  • 目的:提高性能(编译器可以做更多优化)和保证数据安全。

​​2.2.4 readonly实例成员 (struct内的方法)​​

  • ​规则:​​ 贴在 struct内部的一个方法上。表示​​这个方法保证不会动struct实例里的任何东西(不会修改字段)。​

  • 编译器会检查这个方法确实没有修改任何字段。

  • 目的:告诉编译器和使用者这个方法很安全,不会改变结构状态。也可以用在属性的 get访问器上,表示 get不会改变对象状态(即使它内部可能有计算)。

    public struct Point
    {public int X;// readonly 方法:只读取字段,不修改public readonly void Print(){Console.WriteLine($"({X})"); // 允许:读取字段}// readonly 方法:尝试修改字段public readonly void Move(int deltaX){X += deltaX; //编译错误 CS1604: 无法对只读成员赋值}// readonly 方法:尝试修改整个实例public readonly void Reset(){this = new Point(); //编译错误 CS1604: 无法对只读成员赋值}// readonly 属性 get 访问器:只读访问字段public readonly double Width => X; // 允许// readonly 属性 get 访问器:基于字段计算public readonly double Area{get { return X * X; } // 允许:读取字段计算}
    }

补充:

  • 传统属性声明(带显式 get)​
public double Width
{get { return X; } // 显式 get 访问器
}
  • 表达式体属性(C# 6+ 引入的简写)​
public double Width => X; // 等同于上面的写法

2.2.5 ref readonly返回值

  • ​规则:​​ 一个方法返回一个​​引用​​(指向内存位置的指针),但同时加上 readonly表示:“可以通过这个指针​​看​​那个地方的东西,但​​绝对不允许​​通过这个指针去​​修改​​!”

  • 目的:避免复制整个对象(特别是大的结构体),提高性能 ,同时保证调用者不能意外修改原始数据。

举例:

public struct LargeData
{public int Value1;public int Value2;// ...假设还有很多其他字段,使得这个结构体很大
}public class DataHolder
{private LargeData _data = new LargeData { Value1 = 10, Value2 = 20 };// 返回对内部数据的只读引用(不复制)public ref readonly LargeData GetDataRef() => ref _data;
}

那为什么需要 ref呢

  • 避免复制开销(性能优化)
// 返回副本(复制整个结构体)
public LargeData GetDataCopy() => _data;// 返回引用(不复制)
public ref readonly LargeData GetDataRef() => ref _data;
  • 提供直接访问原始数据的能力
public class DataHolder
{private LargeData _data = new LargeData { Value = 42 };// 返回引用public ref readonly LargeData GetDataRef() => ref _data;
}// 使用
var holder = new DataHolder();
ref readonly var data = ref holder.GetDataRef();
Console.WriteLine(data.Value); // 直接访问原始数据

ref和 ref readonly的区别

特性

ref

ref readonly

​能否修改数据​

✅ 可以修改

❌ 不能修改

​用途​

需要修改原始数据时

需要高效读取但不修改时

​安全性​

可能意外修改数据

编译器强制保护原始数据

补充:在之前的文章中也提及过ref,主要是在foreach语句中的使用,感兴趣的可以点击链接去阅读2.2章节

C#知识学习-005(迭代语句)https://blog.csdn.net/c20220924/article/details/149833524?ops_request_misc=%257B%2522request%255Fid%2522%253A%25225bd6f7cb5e484c290bd82e33c8b29440%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=5bd6f7cb5e484c290bd82e33c8b29440&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-149833524-null-null.nonecase&utm_term=ref&spm=1018.2226.3001.4450https://blog.csdn.net/c20220924/article/details/149833524?ops_request_misc=%257B%2522request%255Fid%2522%253A%25225bd6f7cb5e484c290bd82e33c8b29440%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=5bd6f7cb5e484c290bd82e33c8b29440&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-149833524-null-null.nonecase&utm_term=ref&spm=1018.2226.3001.4450

2.2.6 in参数(方法参数上的readonly引用)

  • ​规则:​​ 是 ref readonly参数的语法糖(只读引用参数)

  • 目的:高效传递大型结构体(避免复制),同时保证方法内部不会修改原始数据

举例:

public struct BigStruct
{public int Data1;public int Data2;// ... 其他很多字段
}public class Processor
{// 1. 传值 (复制整个结构体)public void ProcessByValue(BigStruct data){// 可以修改副本,不影响原始数据data.Data1 = 100; }// 2. ref 引用传递 (可修改原始数据)public void ProcessByRef(ref BigStruct data){// 直接修改原始数据!data.Data1 = 100; }// 3. in 只读引用传递 (重点!)public void ProcessByIn(in BigStruct data){// 读取数据 ✅Console.WriteLine(data.Data1);// 尝试修改 ❌ 编译错误!// data.Data1 = 100; // 错误 CS8332: 无法对只读变量赋值}
}

3.比较

readonly vs   const

  • const:​

    • 是​​编译时常量​​。

    • 必须​​在声明时初始化,值必须在写代码时就确定(比如 const int Max = 100;)

    • 值​​绝对不可变​​。编译后,所有用到 Max的地方都被直接替换成 100

    • 只能是基本类型(intstring等)或 null

  • readonly:​

    • 是​​运行时常量​​。值可以在​​运行时​​确定(比如在构造函数里根据当前时间赋值 readonly DateTime Created = DateTime.Now;)。

    • 可以在声明时​​或​​在类的​​构造函数​​中初始化。

    • 初始化后值​​不可变​​。编译后,访问的是那个字段的内存位置。

    • 可以是任何类型。

学到了这里,咱俩真棒,记得按时吃饭生活鸡飞蛋挞~

【本篇结束,新的知识会不定时补充】

感谢你的阅读!如果内容有帮助,欢迎 ​​点赞❤️ + 收藏⭐ + 关注​​ 支持! 😊

http://www.dtcms.com/a/419453.html

相关文章:

  • 有做分期海淘的网站吗苏州建设网站找网络公司
  • 全缓冲和行缓冲
  • 【C++语法】C++11——右值引用
  • 如何给网站做app平台网站建设 厦门
  • 鞍山网站建设工作室北京交易中心网站
  • 现代C++ Lambda表达式:最佳实践、深入理解和资源推荐
  • CUDA 内核中计算全局线程索引的标准方法
  • 刚做网站做多用户还是单用户企业建站的作用是什么
  • CUDA 13.0深度解析:统一ARM生态、UVM增强与GPU共享的革命
  • 佛山外贸网站建设咨询php网站上做微信支付功能
  • 公司网站中新闻中心怎样做优化网页设计图片怎么居中
  • 网站运营经验山东查询网站备案
  • 巴塘网站建设网站开发的论文课题
  • GuavaCache
  • 免费空间如何放网站搜索引擎优化培训免费咨询
  • LeetCode 53 最大子数字和(动态规划)
  • 如何为100Tops机器人“退烧”?世强芯片热管理方案,释放100%算力!
  • 【NodeJS】使用 NVM 安装 Node.js 22 并配置国内镜像加速
  • 边缘计算与AI:移动端设计软件的实时性能突破
  • 芜湖有没有网站建设公司吗wordpress邮件分析插件
  • 网上做外贸都有哪些网站组织架构及营销网络怎么填写
  • 网站建设费开票税收代码模板网站好还是自助建站好
  • 苏州网站建设数据网络wordpress添加广告插件
  • 江西哪里可以做企业网站h5案例网站
  • 洛谷题解——C语言(9.17——9.19)
  • vue3 element-plus自定义el-select后缀图标
  • 突破速度瓶颈:为可道云连接雨云对象存储,实现私人网盘高速上传下载
  • 第二章:模块的编译与运行-6 Compiling and Loading
  • Coze源码分析-资源库-编辑插件-前端源码-核心API
  • 如何做导购网站电子商务网站软件建设的核心是