深入static readonly的安全与内存分配[原创]

问题起源:为了开发帧同步,在写定点数的Vector3类时,想要仿照Unity的Vector3给这个类加一些静态的只读字段,比如说Unity里的Vector3.up等等。

看了Unity源码后,我发现除了Unity的实现方式,还有好几种不同设计的方法(下面会列出)。这就引起了我的好奇心:这些方式有啥区别以及哪个更好,有没有办法再保证安全的情况下提升性能,减少没要的复制?

于是我通过测试与查看IL码(IL码就不贴出来了),做了一个比较(不考虑JIT的优化):

背景

引入一个简化后的例子,作为比较的背景:

public struct Point
{public int x;public int y;public Point(int x, int y){this.x = x;this.y = y;} public void Method() { y += 1; }//此属性会在调用时,复制一份x的值,然后再返回复制的值public int Change => ++x;//1.Unity定义的方式private static readonly Point unity =new Point(1, 1);public static Point Unity => unity;//2.Unity定义的方式去掉了readonlyprivate static Point staticPro =new Point(1, 1);public static Point StaticPro => staticPro;//3.使用static的自动属性public static Point StaticAutoPro => new Point(1, 1);//4.去掉了属性,直接使用static字段public static Point StaticField =new Point(1, 1);//5.使用static+readonly字段public static readonly Point StaticReadonlyField = new Point(1, 1);
}
  • 属性get可以看成是方法,在get里已经复制了一份新的值,再return出去。

1. Unity定义的方式

定义:

//1.Unity定义的方式
private static readonly Point unity = new Point(1, 1);
public static Point Unity => unity;
//调用Unity时会复制一份结构体的值,然后再传出去

使用解析(IL):

//这句会在get中就复制一份新的结构体存入栈a1中。(复制了1次)
var a1 = Point.Unity;//这句会在get中就复制一份新的结构体存入栈内存中,然后再把结构体里x的值复制存入栈b1中(复制了2次)
var b1 = Point.Unity.x;//这句会在get中就复制一份新的结构体存入栈内存中,然后通过栈中结构体的Change属性,复制x的值存入栈c1中(复制了2次)
var c1 = Point.Unity.Change;//这句会在get中就复制一份新的结构体存入栈内存中,再调用其函数。(复制了1次)
Point.Unity.Method();

所以从Unity的情况来看,安全性得到了最好的保障:在类外通过属性保护,只要调用它不管怎样都会复制一份出去操作,不会被篡改。但是这样子当然会损失性能(虽然我看来其实几乎没有,因为用的很少),但也许会有安全和性能兼顾的办法。

在类里是readonly,也有一定的保护作用。除了读取字段不用复制,读取属性/方法也会复制一份(具体可以看深入解析C#里的只读类型的隐式复制)。


2. Unity定义的方式去掉了readonly,也就是static的一个属性

定义:

//2.Unity定义的方式去掉了readonly
private static Point staticPro = new Point(1, 1);
public static Point StaticPro => staticPro;
//调用Unity时会复制一份结构体的值,然后再传出去

使用解析(IL):

//其实在类外使用的情况下,和第一种是一样的,毕竟只是去掉了readonly。
//在类里面使用才会有区别:在类里面修改,调用属性方法等都可能会造成他本身的修改。

所以安全性不如第一种,在类外使用也没有什么性能提升,按我的代码需求感觉是还不如第一种。


3. 使用static的自动属性

定义:

//3.使用static的自动属性
public static Point StaticAutoPro => new Point(1, 1);
//调用Unity时会新建一份结构体的值,然后再传出去

使用解析(IL):

//在get中创建一份结构体存入栈a3中。(创建1次)
var a3 = Point.StaticAutoPro;//在get中创建一份结构体存入栈中,然后再把结构体里x的值复制存入栈b3中(创建1次,复制1次)
var b3 = Point.StaticAutoPro.x;//在get中创建一份结构体存入栈中,然后通过栈中结构体的Change属性,复制x的值存入栈c3中(创建1次,复制1次)
var c3 = Point.StaticAutoPro.Change;//在get中创建一份结构体存入栈中,再调用其函数。(复制了1次)
Point.StaticAutoPro.Method();

这个跟第2种的区别在于:无论是类外还是类里,都会创建一个新的值传出去,而不是复制值。

这种安全性其实可以保证,因为每次都是创建一个新的值(性能应该差别不大),唯一有一点小区别是在类里读的话也要创建一个新的,这个开销其实没必要,但是在类里读的情况也几乎不会发生。


4.去掉了属性,直接使用static字段

定义:

//4.去掉了属性保护,直接使用static字段
public static Point StaticField =new Point(1, 1);

使用解析(IL):

//从字段复制一份结构体存入栈a4中。(复制1次)
var a4 = Point.StaticField;//会将原字段地址加载进栈内存中,然后再把原字段里x的值复制存入栈b4中(复制1次)
var b4 = Point.StaticField.x;//会将原字段地址加载进栈内存中,然后通过原字段的Change属性,复制x的值存入栈c4中(复制1次)
var c4 = Point.StaticField.Change;//会将原字段地址加载进栈内存中,然后直接调用(0复制)
Point.StaticField.Method();

这一种性能会好很多,但是几乎没有安全性可言,类里类外都可以任意的访问修改。即使我的代码里不会去修改,但也不能将其这样设计。


5.使用static+readonly字段

定义:

//5.使用static+readonly字段
public static readonly Point StaticReadonlyField = new Point(1, 1);

使用解析(IL):

//会复制一份新的结构体存入栈a5中。(复制了1次)
var a5 = Point.StaticReadonlyField;//会将原字段地址加载进栈内存中,然后再把原字段里x的值复制存入栈b5中(复制1次)
var b5 = Point.StaticReadonlyField.x;//会复制一份新的结构体存入栈内存中,然后通过栈中结构体的Change属性,复制x的值存入栈c5中(复制了2次)
var c5 = Point.StaticReadonlyField.Change;//会复制一份新的结构体存入栈内存中,再调用其函数。(复制了1次)
Point.StaticReadonlyField.Method();

虽然没有属性的保护,但它有readonly的保护:在类外或类里调用属性/方法时,都会复制一次,但在读取变量时不会复制。这样子在性能上有了提升,安全性也得到了一定的保障。

其实跟第一种(Unity)比起来,唯一的区别是:这一种在读变量时会提高性能不复制,但是也不能修改变量,我的需求(Vector3.up)肯定是不会修改的,所以最后我选择了这一种方式。


创作:Hyu

深入C#里static readonly的安全性与内存分配[原创]相关推荐

  1. 从内存分配角度分析c和java里的static 关键字.

    即使作为Java的初学者, 对this 和 static 这两个关键字都不会陌生. 其实也不难理解: this 关键字:  指的是对象的本身(注意不是类本身)  跟.net 语言的Me 关键字类似. ...

  2. const和static readonly 区别

    我们都知道,const和static readonly的确很像:通过类名而不是对象名进行访问,在程序中只读等等. 在多数情况下可以混用. 二者本质的区别在于,const的值是在编译期间确定的,因此只能 ...

  3. const 与 static readonly 的区别

    来作个例子吧: 先创建一个类库ClassLibrary1.dll using System; namespace ClassLibrary1 {     /// <summary>     ...

  4. C#中const和static readonly 的区别

    我们都知道,const和static readonly的确很像:通过类名而不是对象名进行访问,在程序中只读等等.在多数情况下可以混用. 二者本质的区别在于,const的值是在编译期间确定的,因此只能在 ...

  5. C# static readonly 与 const 的区别

    static readonly 与 const 的区别: const 表达式的值是在编译时形成的: static readonly 表达式的值直到程序运行时才形成: 转载于:https://www.c ...

  6. C#Const与static readonly的区别

    前言: Const与readonly我们应该都用过,我们只知道这个关键字很像,都是通过类型访问,并且在程序中都是只读,但是很少有人能分清楚两个的差别,今天我们来细谈Const与readonly. Co ...

  7. JVM—堆栈 堆 方法区 静态区 final static 内存分配

    原文作者:一夜丶鱼龙舞 原文地址:JAVA 堆栈 堆 方法区 静态区 final static 内存分配 详解(转) 一.栈(stack)和堆(heap) (1)内存分配的策略 按照编译原理的观点,程 ...

  8. [转载] JAVA 堆栈 堆 方法区 静态区 final static 内存分配 详解

    参考链接: 在Java中为静态最终static final变量分配值 转载来源:https://blog.csdn.net/peterwin1987/article/details/7571808 J ...

  9. JAVA基础-栈与堆,static、final修饰符、内部类和Java内存分配

    JAVA基础-栈与堆,static.final修饰符.内部类和Java内存分配 发布时间: 2013/01/12 22:29 QQ空间 新浪微博 腾讯微博 人人网 豆瓣网 百度空间 百度搜藏 开心网 ...

最新文章

  1. elasticsearch 文档操作
  2. laravel 发送带附件的邮件
  3. uiautomator环境搭建所遇问题汇总
  4. eclipse svn 与资源库同步 符号说明
  5. Java方法的静态绑定与动态绑定讲解
  6. 机器学习爬大树之决策树(ID3,C4.5)
  7. JSP Servlet Mysql学生宿舍管理系统
  8. select count(*)和select count(1)的区别
  9. python开发图片_python实现简单的图片隐写术
  10. 并行算法设计与性能优化_MySQL高性能优化规范建议,从设计,命名,开发等一条线的建议...
  11. 【动态规划】LeetCode 53. Maximum Subarray
  12. 机器学习 - [集成学习]Bagging算法的编程实现
  13. scala数组与java数组对比
  14. MapTask工作机制图解
  15. opta球员大数据预测胜负_大数据预测4个特征,10个典型行业
  16. 锐浪报表 Grid++Report uniGUI Web表格打印
  17. linux 命令 查询丢包率,linux测试丢包率的命令 linux查看丢包率命令
  18. 一个屌丝程序员的青春(五一)
  19. 逆向看C++ new申请堆对象的构造,析构函数调用
  20. dell服务器 指示灯_Dell PowerEdge服务器或PowerVault存储诊断LED指示灯(QuadPack)故障排除...

热门文章

  1. 软考成绩什么时候出来?
  2. Android接收读取短信内容
  3. UVA1335 Beijing Guards
  4. java删除修改的代码怎么写_Java代码增删查改完整流程
  5. 人体解剖学标本长廊的构成、管理及其优势
  6. 个人笔记,深入理解 JVM,很全!
  7. Oracle同义词总结
  8. Linux解压缩时报错: Error is not recoverable: exiting now
  9. LifeSmart云起局域网直接控制向往背景音乐
  10. Rust 官方入门程序(a Guessing Game)解读