目录: 1,文件操作 2,Debug、Trace类 3,条件编译 4,MethodImpl 特性 5,CLSComplianAttribute 6,必要时自定义类型别名 最近在阅读 .NET Core Runtime 的源码,参考大佬的代码,学习编写技巧和提高代码水平。学习过程中将学习心得和值得应用到项目中的代码片段记录下来,供日后查阅。 1,文件操作这段代码在 当使用文件时,要提前判断文件路径是否存在,日常项目中要使用到文件的地方应该不少,可以统一一个判断文件是否存在的方法: public static bool Exists(string? path) { try { // 可以将 string? 改成 string if (path == null) return false; if (path.Length == 0) return false; path = Path.GetFullPath(path); // After normalizing, check whether path ends in directory separator. // Otherwise, FillAttributeInfo removes it and we may return a false positive. // GetFullPath should never return null Debug.Assert(path != null, "File.Exists: GetFullPath returned null"); if (path.Length > 0 && PathInternal.IsDirectorySeparator(path[^1])) { return false; } return InternalExists(path); } catch (ArgumentException) { } catch (NotSupportedException) { } // Security can throw this on ":" catch (SecurityException) { } catch (IOException) { } catch (UnauthorizedAccessException) { } return false; } 建议项目中对路径进行最终处理的时候,都转换为绝对路径: Path.GetFullPath(path) 当然,相对路径会被 .NET 正确识别,但是对于运维排查问题和各方面考虑,绝对路径容易定位具体位置和排错。 在编写代码时,使用相对路径,不要写死,提高灵活性;在运行阶段将其转为绝对路径; 上面的 2,读取文件这段代码在 System.Private.CoreLib 中。 有个读取文件转换为 byte[] 的方法如下: public static byte[] ReadAllBytes(string path) { // bufferSize == 1 used to avoid unnecessary buffer in FileStream using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1)) { long fileLength = fs.Length; if (fileLength > int.MaxValue) throw new IOException(SR.IO_FileTooLong2GB); int index = 0; int count = (int)fileLength; byte[] bytes = new byte[count]; while (count > 0) { int n = fs.Read(bytes, index, count); if (n == 0) throw Error.GetEndOfFile(); index += n; count -= n; } return bytes; } } 可以看到 FileStream 的使用,如果单纯是读取文件内容,可以参考里面的代码: FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1) 上面的代码同样也存在 private static byte[] InternalReadAllBytes(String path, bool checkHost) { byte[] bytes; // 此 FileStream 的构造函数不是 public ,开发者不能使用 using(FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileStream.DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, checkHost)) { // Do a blocking read int index = 0; long fileLength = fs.Length; if (fileLength > Int32.MaxValue) throw new IOException(Environment.GetResourceString("IO.IO_FileTooLong2GB")); int count = (int) fileLength; bytes = new byte[count]; while(count > 0) { int n = fs.Read(bytes, index, count); if (n == 0) __Error.EndOfFile(); index += n; count -= n; } } return bytes; } 这段说明我们可以放心使用 如果我们手动 .NET 文件流缓存大小默认是 internal const int DefaultBufferSize = 4096; 这段代码在 File 类中定义,开发者不能设置缓存块的大小,大多数情况下,4k 是最优的块大小。 ReadAllBytes 的文件大小上限是 2 GB。 3,Debug 、Trace类这两个类的命名空间为 Debug 中的所有函数都不会在 Release 中有效,并且所有输出流不会在控制台显示,必须注册侦听器才能读取这些流。 Debug 可以打印调试信息并使用断言检查逻辑,使代码更可靠,而不会影响发运产品的性能和代码大小。 这类输出方法有 Write 、WriteLine 、 WriteIf 和 WriteLineIf 等,这里输出不会直接打印到控制台。 如需将调试信息打印到控制台,可以注册侦听器: ConsoleTraceListener console = new ConsoleTraceListener(); Trace.Listeners.Add(console); 注意, .NET Core 2.x 以上 Debug 没有 Listeners ,因为 Debug 使用的是 Trace 的侦听器。 我们可以给 Trace.Listeners 注册侦听器,这样相对于 Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); Debug.WriteLine("aa"); .NET Core 中的监听器都继承了 TraceListener,如 TextWriterTraceListener、ConsoleTraceListener、DefaultTraceListener。 如果需要输出到文件中,可以自行继承 示例: TraceListener listener = new DelimitedListTraceListener(@"C:\debugfile.txt"); // Add listener. Debug.Listeners.Add(listener); // Write and flush. Debug.WriteLine("Welcome"); 处理上述方法输出控制台,也可以使用 ConsoleTraceListener console=... ...Listeners.Add(console); // 等效于 var console = new TextWriterTraceListener(Console.Out) 为了格式化输出流,可以使用 一下属性控制排版:
// 1. Debug.WriteLine("One"); // Indent and then unindent after writing. Debug.Indent(); Debug.WriteLine("Two"); Debug.WriteLine("Three"); Debug.Unindent(); // End. Debug.WriteLine("Four"); // Sleep. System.Threading.Thread.Sleep(10000); One Two Three Four
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); int value = -1; // A. // If value is ever -1, then a dialog will be shown. Debug.Assert(value != -1, "Value must never be -1."); // B. // If you want to only write a line, use WriteLineIf. Debug.WriteLineIf(value == -1, "Value is -1."); ---- DEBUG ASSERTION FAILED ---- ---- Assert Short Message ---- Value must never be -1. ---- Assert Long Message ---- at Program.Main(String[] args) in ...Program.cs:line 12 Value is -1.
在 IDE 中运行程序时,使用 在非 IDE 环境下,程序会输出一些信息,但不会有中断效果。 Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); Trace.Assert(false); Process terminated. Assertion Failed at Program.Main(String[] args) in C:\ConsoleApp4\Program.cs:line 44 个人认为,可以将 Debug、Trace 引入项目中,与日志组件配合使用。Debug、Trace 用于记录程序运行的诊断信息,便于日后排查程序问题;日志用于记录业务过程,数据信息等。
public static void Fail(string message) { if (UseGlobalLock) { lock (critSec) { foreach (TraceListener listener in Listeners) { listener.Fail(message); if (AutoFlush) listener.Flush(); } } } else { foreach (TraceListener listener in Listeners) { if (!listener.IsThreadSafe) { lock (listener) { listener.Fail(message); if (AutoFlush) listener.Flush(); } } else { listener.Fail(message); if (AutoFlush) listener.Flush(); } } } } 4,条件编译
如果使用特性进行条件编译标记,在开发过程中就可以留意到这部分代码。 [Conditional("DEBUG")] 例如,当使用修改所有引用-修改一个类成员变量或者静态变量名称时,
代码片段只能使用 5,MethodImpl 特性此特性在 System.Runtime.CompilerServices 命名空间中,指定如何实现方法的详细信息。 内联函数使用方法可参考 https://www./archives/995 MethodImpl 特性可以影响 JIT 编译器的行为。 无法使用 MethodImpl 可以在方法以及构造函数上使用。 MethodImplOptions 用于设置编译行为,枚举值可组合使用,其枚举说明如下:
意思是说,如果共享的成员已经设置了锁,那么不应该再在 5,CLSCompliantAttribute指示程序元素是否符合公共语言规范 (CLS)。 CLS规范可参考: https://docs.microsoft.com/en-us/dotnet/standard/language-independence https://www./publications/standards/Ecma-335.htm 全局开启方法: 程序目录下添加一个 AssemblyAttribytes.cs 文件,或者打开 obj 目录,找到 AssemblyAttributes.cs 结尾的文件,如 .NETCoreApp,Version=v3.1.AssemblyAttributes.cs,添加: using System;// 这行已经有的话不要加 [assembly: CLSCompliant(true)] 之后就可以在代码中使用 局部开启: 也可以放在类等成员上使用: [assembly: CLSCompliant(true)] 您可以将特性应用于 CLSCompliantAttribute 下列程序元素:程序集、模块、类、结构、枚举、构造函数、方法、属性、字段、事件、接口、委托、参数和返回值。 但是,CLS 遵从性的概念仅适用于程序集、模块、类型和类型的成员。 程序编译时默认不会检查代码是否符合 CLS 要求,但是如果你的可以是公开的(代码共享、Nuget 发布等),则建议使用使用 在团队开发中以及内部共享代码时,高质量的代码尤为重要,所以有必要使用工具检查代码,如 roslyn 静态分析、sonar 扫描等,也可以使用上面的特性,自动使用 CLS 检查。 CLS 部分要求:
我们可以编译以下代码,尝试使用 [assembly: CLSCompliant(true)] [CLSCompliant(true)] public class Test { public void MyMethod() { } public void MYMETHOD() { } } IDE 中会警告:warning CS3005: 仅大小写不同的标识符“Test.MYMETHOD()”不符合 CLS,编译时也会提示 Warn。当然,不会阻止编译,也不会影响程序运行。 总之,如果要标记一个程序集 CLS 规范,可以使用
如果偏偏要写不符合规范的代码,则可以使用 6,必要时自定义类型别名C# 也可以定义类型别名。 using intbyte = System.Int32; using intkb = System.Int32; using intmb = System.Int32; using intgb = System.Int32; using inttb = System.Int32; byte[] fileByte = File.ReadAllBytes("./666.txt"); intmb size = fileByte.Length / 1024; 一些情况下,使用别名可以提高代码可读性。真实项目不要使用以上代码,我只是写个示例,这并不是合适的应用场景。 今天学习 Runtime 的代码就到这里为止。 |
|