[C#]跨模块的可选参数与常量注意事项

假设某个DLL里有这么一个类:

1 // Lib.dll
2 public class Lib
3 {
4     public const string VERSION = "1.0";
5     public static void PrintVersion(string version = "1.0")
6     {
7         Console.WriteLine(version);
8     }
9 }

然后有这么个调用方:

// Program.exe
class Program
{
    static void Main()
    {
        Console.WriteLine(Lib.VERSION);
        Lib.PrintVersion();
        Console.Read();
    }
}

Program.exe的运行结果是显而易见的:

1.0
1.0

过了一段时间,Lib更新版本了:

// Lib.dll
public class Lib
{
    public const string VERSION = "2.0";
    public static void PrintVersion(string version = "2.0")
    {
        Console.WriteLine(version);
    }
}

把Lib.dll重新编译确保Program.exe引用了最新的DLL,然后再运行Program.exe,结果:

1.0
1.0

重新编译Program.exe以后再次运行:

2.0
2.0

发现问题了吧,调用方必须重新编译才能确保可选参数和常量的值是最新的。

原因是这样的:

 1 .method private hidebysig static 
 2     void Main () cil managed 
 3 {
 4     // Method begins at RVA 0x2050
 5     // Code size 30 (0x1e)
 6     .maxstack 8
 7     .entrypoint
 8 
 9     IL_0000: ldstr "1.0"
10     IL_0005: call void [mscorlib]System.Console::WriteLine(string)
11     IL_000a: ldstr "1.0"
12     IL_000f: call void CsConsole.Program/Lib::PrintVersion(string)
13     IL_0014: call int32 [mscorlib]System.Console::Read()
14     IL_0019: pop
15     IL_001a: ret
16 }

这是第一次编译之后Program.Main方法的IL,Lib.VERSION完全被编译成了字面量"1.0"(第10行),而第11行的"1.0"是来自(第一次编译时的)Lib.dll的元数据。
显然,不管怎么更新Lib的代码,只要不重新编译Program,这里的两个值就没办法得到更新,而实际的生产环境中经常没法保证调用方会被重新编译。
至于为什么要这样编译,我是这么理解的。
常量的值是在常量池里待着的,通过类的成员去取值显然是不如直接从常量池取来得方便快捷。
而可选参数的实现方式,则是在编译时提前进行判断与赋值,节省了运行时的时间。

解决方法
对于可选参数,《CLR via C#》建议的方法是这样的:

1 public static void PrintVersion(string version = null)
2 {
3     if (version == null)
4     {
5         version = "1.0";
6     }
7     Console.WriteLine(version);
8 }

很显然这段代码的行为与之前相比是有变化的,但是大多数情况下确实可以解决值更新的问题,但是也带来了运行时效率的问题。

对于常量,从我使用的示例就可以看出来,版本号这类的值不应该定义为常量,使用readonly可以达到目的。
而对于真正的常量,则不应该轻易地变更它的值。

最后嘛,Java虽然没有const,但是static final也有同样的表现,同样需要注意这一点。

要是觉得本文还算有点意思就在右下角点个推荐呗~
原文地址:https://www.cnblogs.com/vd630/p/4559118.html