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

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

然后有这么个调用方:

// 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

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

原因是这样的:

 .method private hidebysig static
void Main () cil managed
{
// Method begins at RVA 0x2050
// Code size 30 (0x1e)
.maxstack
.entrypoint IL_0000: ldstr "1.0"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ldstr "1.0"
IL_000f: call void CsConsole.Program/Lib::PrintVersion(string)
IL_0014: call int32 [mscorlib]System.Console::Read()
IL_0019: pop
IL_001a: ret
}

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

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

 public static void PrintVersion(string version = null)
{
if (version == null)
{
version = "1.0";
}
Console.WriteLine(version);
}

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

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

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

[C#]跨模块的可选参数与常量注意事项的更多相关文章

  1. (1)ES6中let,const,对象冻结,跨模块常量,新增的全局对象介绍

    1.let声明变量,var声明变量,而const声明的常量 2.let与var的区别 let可以让变量长期驻扎在内存当作 let的作用域是分块[ {快1  {快2 }  }每个大括号表示一个独立的块 ...

  2. es65 跨模块常量

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. 小知识:C#可选参数的一个陷阱

    一.背景: 互联网行业,为了降低程序维护.升级的部署风险,往往会将程序拆分成很多项目,编译成多个dll部署,这样发布的时候,只需要部署修改过的dll即可.   二.问题: 有一个函数,在很多个地方被使 ...

  4. 通过Queue的构造函数的可选参数maxsize来设定队列长度

    创建一个"队列"对象 import Queuemyqueue = Queue.Queue(maxsize = 10) Queue.Queue类即是一个队列的同步实现.队列长度可为无 ...

  5. c# 方法参数(传值,传引用,ref,out,params,可选参数,命名参数)

       一.方法参数的类型----值类型和引用类型 当方法传递的参数是值类型时,变量的栈数据会完整地复制到目标参数中即实参和形参中的数据相同但存放在内存的不同位置.所以,在目标方法中对形参所做的更改不会 ...

  6. ThinkPHP实现跨模块调用操作方法概述

    ThinkPHP实现跨模块调用操作方法概述 投稿:shichen2014 字体:[增加 减小] 类型:转载   使用 $this 可以调用当前模块内的方法,但是很多情况下经常会在当前模块中调用其他模块 ...

  7. C# 方法的可选参数、命名参数

    原文 http://www.cnblogs.com/lonelyxmas/admin/EditPosts.aspx?opt=1 C#方法的可选参数是.net 4.0最新提出的新的功能,对应简单的重载可 ...

  8. 可选参数、命名参数、.NET的特殊类型、特性

    1.可选参数和命名参数    1.1可选参数        语法:            [修饰符] 返回类型 方法名(必选参数n,可选参数n)        注意:            1.必选参 ...

  9. C# 4.0 可选参数 和 命名参数

    可选参数 可选参数是 C# 4.0 提出来的,当我们调用方法,不给这个参数(可选参数)赋值时,它会使用我们定义的默认值. 需要注意的是: (1)可选参数必须位于所有必选参数的后面: (2)可选参数必须 ...

随机推荐

  1. Cannot modify header information问题的解决方法【新浪云经常遇到的错误】

    我做了一个统一的出错提示函数,在函数执行里面,先处理出错的地址写入cookie以方便用户登陆以后可以直接跳转到要执行的这个页面,可是发现在服务器上测试时,竟然提示本地没有出现的错误: Warning: ...

  2. Hibernate 框架学习

    什么是hibernate框架? 1.它是应用在javaee 三层架构中的dao层 它的底层就是JDBC 它对JDBC进行了封装,好处就是不用写jdbc的代码,和sql语句,它是一个开源的轻量级框架,现 ...

  3. Java中Io类-File类的构造方法

    package com.hxzy.IOSer;import java.io.*;public class Demo02 { public static void main(String[] args) ...

  4. Windows文件共享,报错"该用户已禁用"解决方案

    文章背景:学校机器有俩个用户think和adminsitrator.默认administrator禁用,think属于administrators组. 经排错得问题出现情况.因为同学们都是Think用 ...

  5. Java中常见的jar包及其主要用途

    jar包        用途 axis.jar     SOAP引擎包 commons-discovery-0.2.jar     用来发现.查找和实现可插入式接口,提供一些一般类实例化.单件的生命周 ...

  6. 考试题 T2

    题意分析 首先 要求起点终点不连通 再结合数据范围 就是最小割了 首先我们可以建一个图出来 如果\(x\)可以到\(y\)的话 那么我们就从\(x\)向\(y\)连一条代价为\(h[x]-h[y]+1 ...

  7. SpringMvc redirect

    SpringMVC redirect 核心 首先网上百度到的资源基本已经够用, 留作记录. SpringMVC-redirect重定向跳转传值 虽然这哥们也是转的, 但又没有留源地址. 因此 ... ...

  8. leetcode-77-组合

    题目描述: 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合. 示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], ...

  9. 架构师养成记--30.Redis环境搭建

    Redis的安装 下载地址http://redis.io/download 安装步骤: 首先需要安装gcc,把下载好的redis-3.0.0-rc2.tar.gz 放到 /usr/local 文件夹下 ...

  10. FPGA实战操作(2) -- PCIe总线(协议简述)

    目录 1. PCIe基础知识 2. 事务层协议 2.1 数据包结构 2.2 帧头含义详述 3. 报文举例 3.1 寄存器读报文 3.2 完成报文 4. 机制简述 4.1 Non-Posted和Post ...