C#条件编译

官方参考

网址:https://learn.microsoft.com/zh-CN/dotnet/csharp/language-reference/preprocessor-directives

=======================================================================================

C#利用条件编译判断.NET平台及版本的办法,NET5标准符号清单及使用经验

作者: zyl910

一、原初

.NET平台很早就提供了条件编译的语法(#if)。
但是当时官方未制定标准的条件编译符号(Conditional compilation symbols)的名称,而是让各程序自行约定。
由于早期只有“.NET Framework”这一种平台,且每次升级都是向下兼容的。没有标准的标准的预处理器符号名,确实能减少复杂度。

二、混乱期

而到了.NET 4.0、VS2010 的时代,除了“.NET Framework”平台外,还多了“Silverlight”、“XBox”、“Windows Phone 7”等平台。
不久还出现了 PCL(Portable Class Libraries,可移植库)这样能在多个平台上的库。且.NET 开始支持“WinRT/UWP”、“Android”、“iOS”等平台。
此时条件编译就很重要了,可利用条件编译对各平台做不同的处理。且有时为了避免编译失败或做降级处理,需要判断平台的版本。

VS2015开始支持共享项目(Shared Project),能在代码窗口随时使用下拉框来切换平台版本。对条件编译的需求越来越大了。

可是由于此时没有统一的条件编译符号名称的约定,大家各自为政。导致代码的可读性、可移植性很差。
随着开源代码的传播,符号名称逐渐形成了一些共识。这一问题稍微有了好转。

但还有一个更棘手的问题——C#里的条件编译只能进行布尔(bool)检查,不支持版本数值比较,导致判断版本很费劲。

2.1 仅考虑“.NET Standard”平台时的条件判断

例如有一段代码,需要在“.NET Standard 1.3”以上环境运行。最开始可以这样写条件编译的判断:

#if NETSTANDARD1_3
    Console.WriteLine("Run on .NET Standard 1.3+");
#endif

注意“.NET Standard”是不断升级的,目前最新是 2.1版。于是条件编译的判断需写成这样:

#if (NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 || NETSTANDARD2_0 || NETSTANDARD2_1)
    Console.WriteLine("Run on .NET Standard 1.3+");
#endif

若“.NET Standard”以后发布了新版本,那么这个条件编译判断得同步修改。

2.2 同时支持“.NET Standard”、“.NET Framework”平台

“.NET Framework”是兼容“.NET Standard”的,常用版本的对应关系是——

  • .NET Standard 1.3:.NET Framework 4.6
  • .NET Standard 1.4~2.0:.NET Framework 4.6.1
  • .NET Standard 2.1:.NET Framework 不支持

例如在“.NET Standard 1.3”上运行的代码,是能在1.3对应的“.NET Framework 4.6”上运行的。于是条件编译的判断改写成这样:

#if (NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 || NETSTANDARD2_0 || NETSTANDARD2_1) || (NET46)
    Console.WriteLine("Run on .NET Standard 1.3+, .NET Framework 4.6+");
#endif

注意“.NET Framework”是不断升级的,目前最新是 4.8版。于是条件编译的判断该写成这样:

#if (NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 || NETSTANDARD2_0 || NETSTANDARD2_1) || (NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48)
    Console.WriteLine("Run on .NET Standard 1.3+, .NET Framework 4.6+");
#endif

若“.NET Standard”、“.NET Framework”任一发布了新版本,那么这个条件编译判断得同步修改。
可见,进行多平台的版本判断,条件编译会写的很冗长。而且随着版本更新,得修改条件加新版本的符号,无法一劳永逸。

三、NET5的统一

到了NET5,官方终于制订了标准的条件编译符号。详见官方文档《SDK 样式项目中的目标框架》中的“.NET 目标框架的预处理器符号的完整列表”: https://docs.microsoft.com/zh-cn/dotnet/standard/frameworks#how-to-specify-a-target-framework

  • .NET Framework: NETFRAMEWORK, NET48, NET472, NET471, NET47, NET462, NET461, NET46, NET452, NET451, NET45, NET40, NET35, NET20
  • .NET Standard: NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, NETSTANDARD1_1, NETSTANDARD1_0
  • .NET 5 及更高版本(和 .NET Core): NET, NET6_0, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0

而且官方还贴心给出“_OR_GREATER”后缀的符号,用于简化版本判断。例如“NETSTANDARD1_3_OR_GREATER”表示“.NET Standard 1.3”或更高版本。

  • .NET Framework: NET48_OR_GREATER, NET472_OR_GREATER, NET471_OR_GREATER, NET47_OR_GREATER, NET462_OR_GREATER, NET461_OR_GREATER, NET46_OR_GREATER, NET452_OR_GREATER, NET451_OR_GREATER, NET45_OR_GREATER, NET40_OR_GREATER, NET35_OR_GREATER, NET20_OR_GREATER
  • .NET Standard: NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, NETSTANDARD1_6_OR_GREATER, NETSTANDARD1_5_OR_GREATER, NETSTANDARD1_4_OR_GREATER, NETSTANDARD1_3_OR_GREATER, NETSTANDARD1_2_OR_GREATER, NETSTANDARD1_1_OR_GREATER, NETSTANDARD1_0_OR_GREATER
  • .NET 5 及更高版本(和 .NET Core): NET6_0_OR_GREATER, NET5_0_OR_GREATER, NETCOREAPP3_1_OR_GREATER, NETCOREAPP3_0_OR_GREATER, NETCOREAPP2_2_OR_GREATER, NETCOREAPP2_1_OR_GREATER, NETCOREAPP2_0_OR_GREATER, NETCOREAPP1_1_OR_GREATER, NETCOREAPP1_0_OR_GREATER

有了这些标准符号后,刚才的条件编译判断便可以写的很简单了。

#if (NETSTANDARD1_3_OR_GREATER) || (NET46_OR_GREATER)
    Console.WriteLine("Run on .NET Standard 1.3+, .NET Framework 4.6+");
#endif

四、使用经验

NET5虽然好,但是有一些旧项目短期内不能升级.NET版本、不能升级VS开发环境。且有时我们需开发支持旧平台的类库。
此时VS不会像NET5那样,自动为我们提供标准的条件编译符号。

于是我们得自立更生,手工配置好条件编译符号。可参考NET5标准的条件编译符号,来进行配置,这样能便于未来的平滑升级。
“_OR_GREATER”后缀的符号虽然好用,但对于手工配置条件编译符号来说,太麻烦了。故可以不用。

为了简化配置条件编译符号的配置,建议仅配置当前版本的符号。例如——

  • 对于“.NET Standard 1.3”的项目,仅需配置“NETSTANDARD;NETSTANDARD1_3”。
  • 对于“.NET Framework 4.6”的项目,仅需配置“NETFRAMEWORK;NET46”。

由于旧版本的版本号已确定,仅是新版本的版本号无法确定。于是可以考虑反向进行条件编译判断,先判断出不兼容的旧版本,这样便能一劳永逸,即使版本升级也有效。
“.NET Framework”的历史很长,若将所有的旧版本都列上,那会太冗长了。考虑到.NET 4.0时代才有多平台概念,故一般情况下,向前兼容只需做到.NET 4.0。有特殊需求时,才考虑支持更旧的版本。

根据这些经验,刚才的条件编译判断,可写成这样:

#if (NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2) || (NET40 || NET45 || NET451 || NET452)
    // .NET Standard <=1.2, .NET Framework <=4.5.2
#else
    Console.WriteLine("Run on .NET Standard 1.3+, .NET Framework 4.6+");
#endif

(完)

 

 

出处:https://www.cnblogs.com/zyl910/p/cs_standard_conditional_compilation_symbols.html

=======================================================================================

C#条件编译

本文导读:

C#的预处理器指令从来不会转化为可执行代码的命令,但是会影响编译过程的各个方面,常用的预处理器指令有#define、#undef、#if,#elif,#else和#endif等等,下面介绍C#中使用#define进行条件编译的实例。

C#中条件编译指令用于按条件包含或排除源文件中的某些部分。在Visual Studio中,会看到被排除的代码显示为灰色。

一、#define可以用来做什么

1、当计划发布两个版本的代码的时候。即基本版和拥有更多版本的企业版,就可以用到条件编译指令;

2、例如同一个文件给silverlight、wpf、winform等使用,并且还考虑Debug和Release等,有大部分代码是一样的;

3、指定函数和属性是否编译到最终产品中去。

二、#define用法

语法:#define 名称

注意:这里名称取Debug,你也可以取其他名称如Dragon

1 #define Debug

说明:

1、Debug可以看做是声明的一个变量,但此变量没有真正的值,存在时#if Debug结果为true,否则为false;

2、#define单独用没什么意义,一般是和#if或者Conditional特性结合使用;

3、#define必须定义在所有using命名空间前面;

4、DebugDEBUG是不同的,C#区分大小写。

三、#define条件编译实例

方式一、使用#if

复制代码
 
 1 #define Dragon
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Text;
 6 using System.Diagnostics;
 7 
 8 namespace ConditionalCompilation
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14 #if Dragon
15             Console.WriteLine("Dragon is defined");
16 #else
17             Console.WriteLine("Dragon is not defined");
18 #endif
19             Console.ReadKey();
20         }
21     }
22 }
 
复制代码

输出结果如下:

如果注释掉 //#define Dragon ,输出结果为:

方式二、使用Conditional特性

我们可以将一些函数隔离出来,使得它们只有在定义了某些环境变量或者设置了某个值之后才能发挥作用,使用Conditional特性的隔离策略要比#if/#endif不容易出错。

复制代码
 
 1 #define Debug
 2 #define Trace
 3 #if (Debug && Trace)
 4 #define DebugAndTrace
 5 #endif
 6 using System;
 7 using System.Collections.Generic;
 8 using System.Linq;
 9 using System.Text;
10 using System.Diagnostics;
11 
12 namespace ConditionalCompilation
13 {
14     class Program
15     {
16         static void Main(string[] args)
17         {
18             Print0();
19             Print1();
20             Print2();
21             Print3();
22             Console.ReadKey();
23         }
24 
25         [Conditional("DEBUG")]
26         static void Print0()
27         {
28             Console.WriteLine("DEBUG is defined");
29         }
30 
31         [Conditional("Debug")]
32         static void Print1()
33         {
34             Console.WriteLine("Debug is defined");
35         }
36 
37         //定义了Debug或者Trace后才会执行此方法
38         //或者的关系
39         [Conditional("Debug"), Conditional("Trace")]
40         static void Print2()
41         {
42             Console.WriteLine("Debug or Trace is defined");
43         }
44 
45         //只有定义了Debug和Trace后才会执行此方法
46         //并且的关系
47         [Conditional("DebugAndTrace")]
48         static void Print3()
49         {
50             Console.WriteLine("Debug and Trace is defined");
51         }
52     }
53 }
 
复制代码

输出结果如下:

说明:

1、代码中没有定义DEBUG,却输出了DEBUG,是因为DEBUG版本,自动定义了DEBUG。“项目——右键——属性——生成选项卡——常规栏”下的定义 DEBUG 常量(U)前面的复选框被选中。当然你可以去掉其选中状态,这样就不会输出DEBUG了。

2、如果Debug和Trace均没有定义,则不会输出Debug or Trace;只有Debug和Trace均定义了,才会输出Debug and Trace。

3、可以给Conditional增加多个属性如示例代码 [Conditional("Debug"), Conditional("Trace")] ,不过多个属性之间的关系是或的关系,即“Debug”或者“Trace”任意一个被定义了,那么对应方法就会被执行。

4、如果需要增加多个与的属性,直接用Conditional是无法实现的,需要借助#if/#endif间接来完成,如示例代码中的组合操作

环境变量(或条件编译符号)的设置方法有三:

1)用#define定义以及#undef取消定义,在所有using命名空间前面定义;

2)用编译器命令行选项(例如,/define:DEBUG),在“项目——右键——属性——生成选项卡——常规栏”下的条件编译符号(Y)中设置(如果多个,可以用英文逗号隔开)。DEBUG版本下,系统默认设置了DEBUG和TRACE;

3)用操作系统外壳程序中的环境变量(例如,set DEBUG=1)。

 

出处:https://www.cnblogs.com/xietianjiao/p/12373281.html

=======================================================================================

读 .NET Core中的文件操作、读取文件、Debug/Trace 类、Conditional条件编译、CLS

目录:

1,文件操作

2,Debug、Trace类

3,条件编译

4,MethodImpl 特性

5,CLSComplianAttribute

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

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

1,文件操作

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

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

        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 正确识别,但是对于运维排查问题和各方面考虑,绝对路径容易定位具体位置和排错。

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

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

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)

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

        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;
        }

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

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

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

internal const int DefaultBufferSize = 4096;

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

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

3,Debug 、Trace类

这两个类的命名空间为 System.Diagnostics,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 注册侦听器,这样相对于 Debug 等效设置侦听器。

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

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

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

示例:

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)

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

属性说明
AutoFlush 获取或设置一个值,通过该值指示每次写入后是否应在 Flush() 上调用 Listeners。
IndentLevel 获取或设置缩进级别。
IndentSize 获取或设置缩进的空格数。
        // 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

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

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

        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.

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

在 IDE 中运行程序时,使用 Debug.Assert()Trace.Assert() 等方法 ,条件为 false 时,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 用于记录程序运行的诊断信息,便于日后排查程序问题;日志用于记录业务过程,数据信息等。

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

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

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,条件编译

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

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

[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,添加:

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

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

局部开启:

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

[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

[assembly: CLSCompliant(true)]
[CLSCompliant(true)]
public class Test
{
    public void MyMethod()
    {
    }
    public void MYMETHOD()
    {
    }
}

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

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

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

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

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 的代码就到这里为止。

 

出处:https://www.cnblogs.com/whuanle/p/14141213.html

posted on 2022-01-25 10:57  jack_Meng  阅读(989)  评论(0编辑  收藏  举报

导航