目录:

1,文件操作

2,Debug、Trace类

3,条件编译

4,MethodImpl 特性

5,CLSComplianAttribute

6,必要时自定义类型别名

最近在阅读 .NET Core Runtime 的源码,参考大佬的代码,学习编写技巧和提高代码水平。学习过程中将学习心得和值得应用到项目中的代码片段记录下来,供日后查阅。

1,文件操作

这段代码在 System.Private.CoreLib 下,对 System.IO.File 中的代码进行精简,供 CLR 使用。

当使用文件时,要提前判断文件路径是否存在,日常项目中要使用到文件的地方应该不少,可以统一一个判断文件是否存在的方法:

  1. public static bool Exists(string? path)
  2. {
  3. try
  4. {
  5. // 可以将 string? 改成 string
  6. if (path == null)
  7. return false;
  8. if (path.Length == 0)
  9. return false;
  10. path = Path.GetFullPath(path);
  11. // After normalizing, check whether path ends in directory separator.
  12. // Otherwise, FillAttributeInfo removes it and we may return a false positive.
  13. // GetFullPath should never return null
  14. Debug.Assert(path != null, "File.Exists: GetFullPath returned null");
  15. if (path.Length > 0 && PathInternal.IsDirectorySeparator(path[^1]))
  16. {
  17. return false;
  18. }
  19. return InternalExists(path);
  20. }
  21. catch (ArgumentException) { }
  22. catch (NotSupportedException) { } // Security can throw this on ":"
  23. catch (SecurityException) { }
  24. catch (IOException) { }
  25. catch (UnauthorizedAccessException) { }
  26. return false;
  27. }

建议项目中对路径进行最终处理的时候,都转换为绝对路径:

  1. Path.GetFullPath(path)

当然,相对路径会被 .NET 正确识别,但是对于运维排查问题和各方面考虑,绝对路径容易定位具体位置和排错。

在编写代码时,使用相对路径,不要写死,提高灵活性;在运行阶段将其转为绝对路径;

上面的 NotSupportedException 等异常是操作文件中可能出现的各种异常情况,对于跨平台应用来说,这些异常可能都是很常见的,提前将其异常类型识别处理,可以优化文件处理逻辑以及便于筛查处理错误。

2,读取文件

这段代码在 System.Private.CoreLib 中。

有个读取文件转换为 byte[] 的方法如下:

  1. public static byte[] ReadAllBytes(string path)
  2. {
  3. // bufferSize == 1 used to avoid unnecessary buffer in FileStream
  4. using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1))
  5. {
  6. long fileLength = fs.Length;
  7. if (fileLength > int.MaxValue)
  8. throw new IOException(SR.IO_FileTooLong2GB);
  9. int index = 0;
  10. int count = (int)fileLength;
  11. byte[] bytes = new byte[count];
  12. while (count > 0)
  13. {
  14. int n = fs.Read(bytes, index, count);
  15. if (n == 0)
  16. throw Error.GetEndOfFile();
  17. index += n;
  18. count -= n;
  19. }
  20. return bytes;
  21. }
  22. }

可以看到 FileStream 的使用,如果单纯是读取文件内容,可以参考里面的代码:

  1. FileStream fs = new FileStream(path,
  2. FileMode.Open,
  3. FileAccess.Read,
  4. FileShare.Read,
  5. bufferSize: 1)

上面的代码同样也存在 File.ReadAllBytes 与之对应, File.ReadAllBytes 内部是使用 InternalReadAllBytes 来处理文档读取:

  1. private static byte[] InternalReadAllBytes(String path, bool checkHost)
  2. {
  3. byte[] bytes;
  4. // 此 FileStream 的构造函数不是 public ,开发者不能使用
  5. using(FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read,
  6. FileStream.DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, checkHost)) {
  7. // Do a blocking read
  8. int index = 0;
  9. long fileLength = fs.Length;
  10. if (fileLength > Int32.MaxValue)
  11. throw new IOException(Environment.GetResourceString("IO.IO_FileTooLong2GB"));
  12. int count = (int) fileLength;
  13. bytes = new byte[count];
  14. while(count > 0) {
  15. int n = fs.Read(bytes, index, count);
  16. if (n == 0)
  17. __Error.EndOfFile();
  18. index += n;
  19. count -= n;
  20. }
  21. }
  22. return bytes;
  23. }

这段说明我们可以放心使用 File 静态类中的函数,因为里面已经处理好一些逻辑了,并且自动释放文件。

如果我们手动 new FileStream ,则要判断一些情况,以免使用时报错,最好参考一下上面的代码。

.NET 文件流缓存大小默认是 4096 字节:

  1. internal const int DefaultBufferSize = 4096;

这段代码在 File 类中定义,开发者不能设置缓存块的大小,大多数情况下,4k 是最优的块大小。

ReadAllBytes 的文件大小上限是 2 GB。

3,Debug 、Trace类

这两个类的命名空间为 System.Diagnostics,Debug 、Trace 提供一组有助于调试代码的方法和属性。

Debug 中的所有函数都不会在 Release 中有效,并且所有输出流不会在控制台显示,必须注册侦听器才能读取这些流

Debug 可以打印调试信息并使用断言检查逻辑,使代码更可靠,而不会影响发运产品的性能和代码大小

这类输出方法有 Write 、WriteLine 、 WriteIf 和 WriteLineIf 等,这里输出不会直接打印到控制台

如需将调试信息打印到控制台,可以注册侦听器:

  1. ConsoleTraceListener console = new ConsoleTraceListener();
  2. Trace.Listeners.Add(console);

注意, .NET Core 2.x 以上 Debug 没有 Listeners ,因为 Debug 使用的是 Trace 的侦听器。

我们可以给 Trace.Listeners 注册侦听器,这样相对于 Debug 等效设置侦听器。

  1. Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
  2. Debug.WriteLine("aa");

.NET Core 中的监听器都继承了 TraceListener,如 TextWriterTraceListener、ConsoleTraceListener、DefaultTraceListener。

如果需要输出到文件中,可以自行继承 TextWriterTraceListener ,编写文件流输出,也可以使用 DelimitedListTraceListener。

示例:

  1. TraceListener listener = new DelimitedListTraceListener(@"C:\debugfile.txt");
  2. // Add listener.
  3. Debug.Listeners.Add(listener);
  4. // Write and flush.
  5. Debug.WriteLine("Welcome");

处理上述方法输出控制台,也可以使用

  1. ConsoleTraceListener console=...
  2. ...Listeners.Add(console);
  3. // 等效于
  4. var console = new TextWriterTraceListener(Console.Out)

为了格式化输出流,可以使用 一下属性控制排版:

属性 说明
AutoFlush 获取或设置一个值,通过该值指示每次写入后是否应在 Flush() 上调用 Listeners。
IndentLevel 获取或设置缩进级别。
IndentSize 获取或设置缩进的空格数。
  1. // 1.
  2. Debug.WriteLine("One");
  3. // Indent and then unindent after writing.
  4. Debug.Indent();
  5. Debug.WriteLine("Two");
  6. Debug.WriteLine("Three");
  7. Debug.Unindent();
  8. // End.
  9. Debug.WriteLine("Four");
  10. // Sleep.
  11. System.Threading.Thread.Sleep(10000);
  1. One
  2. Two
  3. Three
  4. Four

.Assert() 方法对我们调试程序很有帮助,Assert 向开发人员发送一个强消息。在 IDE 中,断言会中断程序的正常操作,但不会终止应用程序。

.Assert() 的最直观效果是输出程序的断言位置。

  1. Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
  2. int value = -1;
  3. // A.
  4. // If value is ever -1, then a dialog will be shown.
  5. Debug.Assert(value != -1, "Value must never be -1.");
  6. // B.
  7. // If you want to only write a line, use WriteLineIf.
  8. Debug.WriteLineIf(value == -1, "Value is -1.");
  1. ---- DEBUG ASSERTION FAILED ----
  2. ---- Assert Short Message ----
  3. Value must never be -1.
  4. ---- Assert Long Message ----
  5. at Program.Main(String[] args) in ...Program.cs:line 12
  6. Value is -1.

Debug.Prinf() 也可以输出信息,它跟 C 语言的 printf 函数行为一致,将后跟行结束符的消息写入,默认行终止符为回车符后跟一个换行符。

在 IDE 中运行程序时,使用 Debug.Assert()Trace.Assert() 等方法 ,条件为 false 时,IDE 会断言,这相当于条件断点。

在非 IDE 环境下,程序会输出一些信息,但不会有中断效果。

  1. Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
  2. Trace.Assert(false);
  1. Process terminated. Assertion Failed
  2. at Program.Main(String[] args) in C:\ConsoleApp4\Program.cs:line 44

个人认为,可以将 Debug、Trace 引入项目中,与日志组件配合使用。Debug、Trace 用于记录程序运行的诊断信息,便于日后排查程序问题;日志用于记录业务过程,数据信息等。

.Assert() 的原理, 在 true 时什么都不做;在 false 时调用 Fail 函数;如果你不注册侦听器的话,默认也没事可做。

.Assert() 唯一可做的事情是等条件为 false 时,执行 Fail 方法,当然我们也可以手动直接调用 Fail 方法,Fail 的代码如下:

  1. public static void Fail(string message) {
  2. if (UseGlobalLock) {
  3. lock (critSec) {
  4. foreach (TraceListener listener in Listeners) {
  5. listener.Fail(message);
  6. if (AutoFlush) listener.Flush();
  7. }
  8. }
  9. }
  10. else {
  11. foreach (TraceListener listener in Listeners) {
  12. if (!listener.IsThreadSafe) {
  13. lock (listener) {
  14. listener.Fail(message);
  15. if (AutoFlush) listener.Flush();
  16. }
  17. }
  18. else {
  19. listener.Fail(message);
  20. if (AutoFlush) listener.Flush();
  21. }
  22. }
  23. }
  24. }

4,条件编译

#if 条件编译会隐藏非条件(#else if)代码,我们开发中很可能会忽略掉这部分代码,当我们切换条件常量到这部分代码时,很可能因为各种原因导致报错。

如果使用特性进行条件编译标记,在开发过程中就可以留意到这部分代码。

  1. [Conditional("DEBUG")]

例如,当使用修改所有引用-修改一个类成员变量或者静态变量名称时,#if 非条件中的代码不会被修改,因为这部分代码“无效”,而且使用 [Conditional("DEBUG")] 的代码则跟条件无关,会被同步修改。

Conditional 特性标记的方法等,在开发过程中保持有效,当在编译时可能被排除。

代码片段只能使用 #if 了,如果是单个方法,则可以使用 Conditional

5,MethodImpl 特性

此特性在 System.Runtime.CompilerServices 命名空间中,指定如何实现方法的详细信息。

内联函数使用方法可参考 https://www.whuanle.cn/archives/995

MethodImpl 特性可以影响 JIT 编译器的行为。

无法使用 MemberInfo.GetCustomAttributes 来获取此特性的信息,即不能通过获取特性的方法获取跟 MethodImpl 有关的信息(反射),只能调用 MethodInfo.GetMethodImplementationFlags()ConstructorInfo.GetMethodImplementationFlags () 来检索。

MethodImpl 可以在方法以及构造函数上使用。

MethodImplOptions 用于设置编译行为,枚举值可组合使用,其枚举说明如下:

枚举 枚举值 说明
AggressiveInlining 256 如可能应将该方法进行内联。
AggressiveOptimization 512 此方法包含一个热路径,且应进行优化。
ForwardRef 16 已声明该方法,但在其他位置提供实现。
InternalCall 4096 该调用为内部调用,也就是说它调用了在公共语言运行时中实现的方法。
NoInlining 8 该方法不能为内联方法。 内联是一种优化方式,通过该方式将方法调用替换为方法体。
NoOptimization 64 调试可能的代码生成问题时,该方法不由实时 (JIT) 编译器或本机代码生成优化(请参阅 Ngen.exe)。
PreserveSig 128 完全按照声明导出方法签名。
Synchronized 32 该方法一次性只能在一个线程上执行。 静态方法在类型上锁定,而实例方法在实例上锁定。 只有一个线程可在任意实例函数中执行,且只有一个线程可在任意类的静态函数中执行。
Unmanaged 4 此方法在非托管的代码中实现。

Synchronized 修饰的方法可以避免多线程中的一些问题,但是不建议对公共类型使用锁定实例或类型上的锁定,因为 Synchronized 可以对非自己的代码的公共类型和实例进行锁定。 这可能会导致死锁或其他同步问题。

意思是说,如果共享的成员已经设置了锁,那么不应该再在 Synchronized 方法中使用,这样双重锁定容易导致死锁以及其他问题。

5,CLSCompliantAttribute

指示程序元素是否符合公共语言规范 (CLS)。

CLS规范可参考:

https://docs.microsoft.com/en-us/dotnet/standard/language-independence

https://www.ecma-international.org/publications/standards/Ecma-335.htm

全局开启方法:

程序目录下添加一个 AssemblyAttribytes.cs 文件,或者打开 obj 目录,找到 AssemblyAttributes.cs 结尾的文件,如 .NETCoreApp,Version=v3.1.AssemblyAttributes.cs,添加:

  1. using System; // 这行已经有的话不要加
  2. [assembly: CLSCompliant(true)]

之后就可以在代码中使用 [CLSCompliant(true)] 特性。

局部开启:

也可以放在类等成员上使用:

  1. [assembly: CLSCompliant(true)]

您可以将特性应用于 CLSCompliantAttribute 下列程序元素:程序集、模块、类、结构、枚举、构造函数、方法、属性、字段、事件、接口、委托、参数和返回值。 但是,CLS 遵从性的概念仅适用于程序集、模块、类型和类型的成员

程序编译时默认不会检查代码是否符合 CLS 要求,但是如果你的可以是公开的(代码共享、Nuget 发布等),则建议使用使用 [assembly: CLSCompliant(true)] ,指明你的库符合 CLS 要求。

在团队开发中以及内部共享代码时,高质量的代码尤为重要,所以有必要使用工具检查代码,如 roslyn 静态分析、sonar 扫描等,也可以使用上面的特性,自动使用 CLS 检查。

CLS 部分要求:

  1. 无符号类型不应成为该类的公共接口的一部分(私有成员可以使用),例如 UInt32 这些属于 C# 的类型,但不是 CLS “标准” 中的。

  2. 指针等不安全类型不能与公共成员一起使用,就是公有方法中都不应该使用 unsafe 代码。(私有成员可以使用)。

  3. 类名和成员名不应重名。虽然 C# 中区分大小写,但是 CLS 不建议同名非重载函数,例如 MYTEST 跟 Mytest。

  4. 只能重载属性和方法,不应重载运算符。重载运算符容易导致调用者不知情时出现程序错误,并且重载运算符要排查问题十分困难。

我们可以编译以下代码,尝试使用 CLSCompliant

  1. [assembly: CLSCompliant(true)]
  2. [CLSCompliant(true)]
  3. public class Test
  4. {
  5. public void MyMethod()
  6. {
  7. }
  8. public void MYMETHOD()
  9. {
  10. }
  11. }

IDE 中会警告:warning CS3005: 仅大小写不同的标识符“Test.MYMETHOD()”不符合 CLS,编译时也会提示 Warn。当然,不会阻止编译,也不会影响程序运行。

总之,如果要标记一个程序集 CLS 规范,可以使用 [assembly: CLSCompliant(true)] 特性。

[CLSCompliant(true)] 特性指示这个元素符合 CLS 规范,这时编译器或者 IDE 会检查你的代码,检查是否真的符合规范。

如果偏偏要写不符合规范的代码,则可以使用 [CLSCompliant(false)]

6,必要时自定义类型别名

C# 也可以定义类型别名。

  1. using intbyte = System.Int32;
  2. using intkb = System.Int32;
  3. using intmb = System.Int32;
  4. using intgb = System.Int32;
  5. using inttb = System.Int32;
  1. byte[] fileByte = File.ReadAllBytes("./666.txt");
  2. intmb size = fileByte.Length / 1024;

一些情况下,使用别名可以提高代码可读性。真实项目不要使用以上代码,我只是写个示例,这并不是合适的应用场景。

今天学习 Runtime 的代码就到这里为止。

C# 好代码学习笔记(1):文件操作、读取文件、Debug/Trace 类、Conditional条件编译、CLS的更多相关文章

  1. C++ 学习笔记之——文件操作和文件流

    1. 文件的概念 对于用户来说,常用到的文件有两大类:程序文件和数据文件.而根据文件中数据的组织方式,则可以将文件分为 ASCII 文件和二进制文件. ASCII 文件,又称字符文件或者文本文件,它的 ...

  2. Python学习笔记(三):文件和集合操作

    python string与list互转 因为python的read和write方法的操作对象都是string.而操作二进制的时候会把string转换成list进行解析,解析后重新写入文件的时候,还得 ...

  3. IOS学习笔记25—HTTP操作之ASIHTTPRequest

    IOS学习笔记25—HTTP操作之ASIHTTPRequest 分类: iOS2012-08-12 10:04 7734人阅读 评论(3) 收藏 举报 iosios5网络wrapper框架新浪微博 A ...

  4. Java NIO 学习笔记(六)----异步文件通道 AsynchronousFileChannel

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  5. tensorflow学习笔记——使用TensorFlow操作MNIST数据(2)

    tensorflow学习笔记——使用TensorFlow操作MNIST数据(1) 一:神经网络知识点整理 1.1,多层:使用多层权重,例如多层全连接方式 以下定义了三个隐藏层的全连接方式的神经网络样例 ...

  6. tensorflow学习笔记——使用TensorFlow操作MNIST数据(1)

    续集请点击我:tensorflow学习笔记——使用TensorFlow操作MNIST数据(2) 本节开始学习使用tensorflow教程,当然从最简单的MNIST开始.这怎么说呢,就好比编程入门有He ...

  7. jQuery学习笔记之DOM操作、事件绑定(2)

    jQuery学习笔记之DOM操作.事件绑定(2) --------------------学习目录------------------------ 4.DOM操作 5.事件绑定 源码地址: https ...

  8. java学习笔记07--日期操作类

    java学习笔记07--日期操作类   一.Date类 在java.util包中定义了Date类,Date类本身使用非常简单,直接输出其实例化对象即可. public class T { public ...

  9. Learning Memory-guided Normality代码学习笔记

    Learning Memory-guided Normality代码学习笔记 记忆模块核心 Memory部分的核心在于以下定义Memory类的部分. class Memory(nn.Module): ...

随机推荐

  1. Oracle数据泵的导入和导出

    前言 今天王子要分享的内容是关于Oracle的一个实战内容,Oracle的数据泵. 网上有很多关于此的内容,但很多都是复制粘贴别人的,导致很多小伙伴想要使用的时候不能直接上手,所以这篇文章一定能让你更 ...

  2. 其实SQL优化调优,就跟吃饭喝水一样简单,教你抓住SQL的本质!

    前言 SOL 优化并不简单,做好 SOL 优化需要掌握数据库体系结构.表和索引设计.高效 SOL法.高级 SOL 语法.多种优化工具等知识,甚至还得分析业务特点,以及了解优化器的缺点.只有建立 SOL ...

  3. FL Studio中的Fruity slicer采样器功能介绍

    本章节采用图文结合的方式来给大家介绍电音编曲软件FL Studio中的Fruity Slicer采样器的功能,感兴趣的朋友可一起来交流哦. Fruity slicer(水果切片器)插件是FL Stud ...

  4. css3系列之transform 详解skew

    skew skewx skewy skewX()  倾斜该元素,里面填的是角度,下面↓ 你会看到,随着元素被倾斜,高度居然不变.聪明的你,一定会知道,高度不变,代表了,Y轴被拉伸了. 跟scale 同 ...

  5. windowsAPI函数操作注册表实现软件开机自启

    注册表的结构 注册表是一个数据库,它的结构同逻辑磁盘类似.注册表包含键(Key),它类似磁盘中的目录,注册表还包含键值(Value),它类似磁盘中的文件.一个键可以包含多个子健和键值,其中键值用于存储 ...

  6. Verilog之阻塞赋值非阻塞赋值

    verilog设计进阶 时间:2014年5月6日星期二 主要收获: 1. 阻塞赋值与非阻塞赋值: 2. 代码测试: 3. 组合逻辑电路和时序逻辑电路. 阻塞赋值与非阻塞赋值: 1. 阻塞赋值" ...

  7. 原创题目 白银之春 Problem and Solution

    白银之春 Solution 比赛用题面.题解.标程和数据生成器都挂在 git@github.com:sun123zxy/spring.git 上. Problem 白银之春 (spring.cpp/. ...

  8. 第6.2节 Python特色的动态可执行方法简介

    一.    基本概念 动态可执行,是指在代码中通过外部输入或代码嵌入的常量字符串包含代码的方式提供Python代码,要求Python执行这些代码.这样就可以达到开放式运行的效果,提高程序的能力和灵活性 ...

  9. 第7.20节 案例详解:Python抽象类之真实子类

    第7.20节 案例详解:Python抽象类之真实子类 上节介绍了Python抽象基类相关概念,并介绍了抽象基类实现真实子类的步骤和语法,本节结合一个案例进一步详细介绍. 一.    案例说明 本节定义 ...

  10. PyQt(Python+Qt)学习随笔:Model/View架构中的Model模型概念

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 Model/View架构中的Model模型Model与数据源通信,为体系结构中的其他组件提供数据接口 ...