@TOC
前言 搞编程的,在工作中学习中,除了培养好的代码习惯、养成自己的一套代码风格 ,还需要多思考如何把代码写的更健壮,碰到bug如何去调试呢 ,与诸君共勉。
一、写代码之前: 1.属性:原本为了防止成员age,设置为公共的之后出现age = -10这种,所以设置GetXxx/SetXxx访问器进行封装,对数据通过两个public函数GetXxx/SetXxx进行合法性检查。但是咱们平时访问age属性,就是一个变量呀,用函数去访问不太符合习惯,所以C#出来一种特殊语法,属性
set访问器有个特殊的隐式参数value
属性运行方式和函数类似,算是一种特殊函数。但是属性的使用方式还是对象或者类,和普通变量一样
属性和他对应的变量同名,但是属性首字母大写Age,变量还是小写age,属性名字可以随便,但是乱起不好识别
2.编译遇到:两个输出文件名解析为同一个输出路径:“obj\x86\Debug\xxxxxx.resources”:使用小乌龟对之前的修改进行删除,不知道是增加了什么 3..NET约定,所有特性的名称都以Attribute结尾,使用时可以省略Attribute 5.适当使用#region…#endregion,可以让代码看着更简洁 二、写代码时: 1.属性和他对应的变量同名,但是属性首字母大写Age,变量还是小写age,属性名字可以随便,但是乱起不好识别 2.所有关于文件、文件流的操作都放到try-catch结构中,关于流的操作应该放在finally块中,确保无论操作成功还是失败,流都被及时释放。我们自己编写一个函数把异常处理和关闭文件的代码封装进去
当一个程序读写文件时,OS通常会阻止其他程序读写这个文件。所以使用完毕后腰及时关闭文件流,否则会出现同步问题
关闭方式:
可以通过finally块中代码关闭
可以通过C#的using语句,当退出using语句时,系统会自动关闭流对象,using适用于需要及时释放资源的代码
fileStream对象的声明代码要放在try语句之前,不然catch块和finally块中会出现fileStream对象的作用域问题
文件(流)总结:封装自己的工具类、关闭等注意事项【有两个注意事项】,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public static void UniversalFileProcess (string path, MyFileProcessCode doSomething ) { FileStream fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite); try { doSomething(fileStream); } catch (Exception e) { Console.WriteLine(e.Message); } finally { if (fileStream != null ) { fileStream.Close(); } } }public delegate void MyFileProcessCode (FileStream file ) ;public static void DoSomething (FileStream fileStream ) { byte [] datas = {100 , 101 , 102 , 103 , 104 , 105 }; fileStream.Write(datas, 0 , datas.Length); }public static void Main (string [] args ) { UniversalFileProcess(@"C:/a.txt" , new MyFileProcessCode(DoSomething)); }
1 2 3 4 5 6 7 8 9 10 static void Main (string [] args ) { UniversalFileProcess(@"C:/a.txt" , delegate (FileStream fileStream)) { byte [] datas = {100 , 101 , 102 , 103 , 104 , 105 }; fileStream.Write(datas, 0 , datas.Length); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using (FileStream fileStream = File.OpenText[path]) { byte [] datas = {100 , 101 , 102 , 103 , 104 , 105 }; fileStream.Write(datas, 0 , datas.Length); } }using (type onj = initialization) { } { type obj = initialization; try { } finally { if (obj != null ) { ((IDisposable)obj).Dispose(); } } }
3.一个生产问题复现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 **DrawGraphThread** 是 null System.NullReferenceException HResult=0x80004003 Message=未将对象引用设置到对象的实例。 Source=WindowsFormsApp1 StackTrace: at WindowsFormsApp1.Form1.suspendThreadButton_Click(Object sender, EventArgs e) in D:\DevelopData\VSExercise\暂时用,用完删除\WindowsFormsApp1\Form1.cs:line 99 at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnClick(EventArgs e) at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent) at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ButtonBase.WndProc(Message& m) at System.Windows.Forms.Button.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.Run(Form mainForm) at WindowsFormsApp1.Program.Main() in D:\DevelopData\VSExercise\暂时用,用完删除\WindowsFormsApp1\Program.cs:line 20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Thread DrawGraphThread;private void startThreadButton_Click (object sender, EventArgs e ) { ThreadStart entryPoint = new ThreadStart(DrawGraph); new Thread(entryPoint).Start(); ...... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Thread DrawGraphThread;private void startThreadButton_Click (object sender, EventArgs e ) { ThreadStart entryPoint = new ThreadStart(DrawGraph); DrawGraphThread = new Thread(entryPoint); DrawGraphThread.Start(); ...... }
1.垃圾回收相关想法
垃圾回收器、垃圾回收算法、标记清除、标记整理、复制
资源指的是一些类似于Windows句柄,数据库连接的非内存的一些本地资源,这些资源无法被运行时本身管理,需要程序员自己去申请和释放。当一些托管类型中对这些资源进行一定的封装的时候,我们就需要妥善的去处理他们,比如说在不用的时候释放到这些资源以免造成资源泄露,怎么知道什么时候不用呢?只有使用这个类型的人知道,所以我们需要在Finalize方法中对资源进行释放。Finalize方法的垃圾对象会在垃圾回收的时候放入一个“垃圾队列”,但是这个垃圾队列并不是说下一次垃圾回收的时候就会调用,而是会在下一次内存压缩(compact)的时候才会去调用,你不知道这货啥时候才会去调用那个Finalize方法,也就是说很可能你这个对象会存活很久(占用内存),并且这个资源一直得不到释放(占用资源)
对集合预先分配大小
使用StringBuilder
值类型代替引用类型,比如用struct代替class
避免Finalizer,使用Dispose Pattern
Finalize方法的垃圾对象会在垃圾回收的时候放入一个“垃圾队列”,但是这个垃圾队列并不是说下一次垃圾回收的时候就会调用,而是会在下一次内存压缩(compact)的时候才会去调用,你不知道这货啥时候才会去调用那个Finalize方法,也就是说很可能你这个对象会存活很久(占用内存),并且这个资源一直得不到释放(占用资源)
Dispose模式,实现IDispose方法来提供一个Dispose方法允许调用者手动去调用Dispose方法释放资源
在以下这几种情况中我们应该为一个类型实现Dispose模式(非完全列表哦)
类型对一些本地资源进行封装,比如类型暴露了一些文件操作的接口,并在内部对文件进行了操作。
如果类型内部有一个Dispose成员,你需要保证这个成员必须在你的对象的生命周期内得到释放。比如说我实现了一个记日志的类,类的内部有一个成员变量是FileStream,我就需要实现Dispose模式来保证这个FileStream能够被释放。
如果你要实现一个基类,基类本身没有本地资源。但是他的子类有那么必须要实现Dispose模式,典型例子就是Stream
缓存、使用泛型
避免内存泄漏
内存泄漏:闭包Closure【作用域在lambda块里的叫做闭包,lambda执行完毕这些变量会被gc销毁】。lambda 语句块里面的东西自产自销满足闭包,你全局的东西不要扔到lambda里,人家lambda表达式自产自销,你放个全局变量_id之后,就不能自产自销了图片可能打不开,可以去我的CSDN看看!
内存泄漏:事件Event。
内存泄漏:静态static。1 2 3 4 5 6 7 8 public class Static { static List<Static> _instances = new List<Static>(); public Static () { _instances.Add(this ); } }
内存泄漏:非托管对象。图片可能打不开,可以去我的CSDN看看!
内存泄漏:Dispose Pattern(SafeHandler)
内存泄漏:Call Dispose
减少大量临时托管对象被频繁创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static class StepFactory { public static IStep GetStep (int stepIndex ) { return _StepMap.ContainsKey(stepIndex) ? _StepMap[stepIndex] : null ; } private static readonly Dictionary<int , IStep> _StepMap = new Dictionary<int , IStep>() { {1 ,new MyStep1() }, {2 ,new MyStep2() }, {3 ,new MyStep3() }, }; }
2.反射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Program { static void Main (string [] args ) { List<MyLearnReflectClass> myLearnReflectClassList = Enumerable.Repeat(new MyLearnReflectClass(), 10000000 ).ToList(); object aux = 0 ; var beginTime2 = System.DateTime.Now; System.Reflection.PropertyInfo propertyInfo = typeof (MyLearnReflectClass).GetProperty("Number" ); foreach (var obj in myLearnReflectClassList) { aux = propertyInfo.GetValue(obj); propertyInfo.SetValue(obj, 3 ); } var endTime2 = System.DateTime.Now; Console.WriteLine(endTime2 - beginTime2); Console.ReadLine(); } }public class MyLearnReflectClass { public int Number { get ; set ; } } ...
3.数据结构
数据结构可以用常见的时间复杂度来衡量,C#中常见的字典、集合、列表的时间复杂度
List 列表是顺序线性表,Add 操作是 O (1) 或 O (N),因为 List 是动态扩容的,在未扩容之前,其 Add 操作是 O (1),而在扩容的时候,Add 操作是 O (N) 的。其 Contains 方法,是按照线性检索的,其复杂度是 O (n)
SortedList 列表是有序线性表,Add 操作是 O(n), 其 Contains 方法是通过二分查找检索元素的,因此复杂度是 O(lg n),其 Containskey 方法也是通过二分查找检索元素,复杂度也是 O(lg n),ContainsValue 方法是使用线性查找元素,复杂度是 O(n)
HashSet 集合类是包含不重复项的无序 hash 表 (非线性),它本身是一个一维数组,但是二维链表结构 (扩展:一维数组的大小总是 2 的 N 次方)。Add 操作是 O(1)或是 O(N)的,原因同 List 集合类。Contains 方法是 O(1)
SortedSet 集合类是基于红黑树实现的,其 Add 方法是 O(lg n),Contains 方法也是 O(lg n)
Dictionary 字典类是 hash 表,Add 操作是 O(1)或是 O(N)的。其 Containskey 方法是 O(1),原因是通过 hash 来查找元素而不是遍历元素。ContainsValue 方法的时间复杂度是 O(N),原因是内部通过遍历 key 来查找 value,而不是通过 hash 来查找。Item [Key] 属性根据 key 来检索 value,其时间复杂度也是 O (1)
SortedDictionary 字典类是基于平衡二叉树实现的,其 Add 方法是 O(lg n),ContainsKey 方法也是 O(lg n),而 ContainsValue 方法则是 O(n)
使用合适的数据结构
局部性原理
时间局部性
如果某个变量或者某句代码这次被访问,那么有可能在不久的将来会被再次访问,这种情况下我们可以把常用的数据加cache来优化访问
如果某个位置的信息被访问,那和他空间上相邻的信息也很有可能被访问,因为大部分时候我咱们代码都是顺序执行,数据也是顺序访问的
空间局部性
具体情况具体分析,时间换空间,空间换时间
4.其他情况,如同步异步、使用不同算法、拆箱装箱【值类型、引用类型】、Try Catch、Dynamic、Lock、互操性、AddRange替换Add+ forngen.exe与编译
巨人的肩膀
周志明老师的凤凰架构
C#高级编程
C#函数式编程
叩响C#之门
组内各位前辈们的指导