C#编译器是如何判定某个变量没有使用过的?

王朝学院·作者佚名  2009-12-15
窄屏简体版  字體: |||超大  

这是我们某个组员在编程过程中提出的疑问。因为这个编译错误很容易避免,所以我一直也没有仔细想过这个问题,直到看过他的代码后才意识到,此问题并不是那么简单的。

先看看这段代码:

代码

class Program

{

static void Main(string[] args)

{

byte[] buf = new byte[1024];

T t = new T();

string str = "1234";

int n = 1234;

int? nn = 1234;

DateTime dt = DateTime.Now;

object o = 1234;

Console.WriteLine("finish");

}

}

class T { }

你觉得这段代码里有几个变量没有使用过呢?

如果从程序员的角度来看,答案应该是所有变量都没有使用过。但编译器给出的结果却有点违反直觉:

变量“str”已赋值,但其值从未使用过

变量“n”已赋值,但其值从未使用过

变量“nn”已赋值,但其值从未使用过

奇怪的地方在于,虽然所有变量都是用同样的方式声明,但编译器却只认为其中一部分没有使用过。这是怎么回事呢?

我们一个一个来分析。首先看看数组,如果使用默认值的话,编译器给出的信息就不同了:

byte[] buf1 = null; // 有警告

byte[] buf2 = new byte[1024]; // 没有警告

这个结果似乎表明,如果参数赋值为null,那么编译器并不会真的执行赋值,并且变量会当作没有使用过。用IL检查的结果也可以证明此说法:对第一行,编译器没有生成任何对应的语句;对第二条则使用了newattr指令来创建数组。

对于自定义的类:

T t1 = null; // 有警告

T t2 = new T(); // 没有警告

这个结果应当是可以理解的(尽管可以理解,但我认为并不好,理由见后)。虽然我们并没有调用该类的任何方法,但是类的构造函数仍然可能执行某些操作,所以只要创建了一个类,编译器就会把它当作已经使用过的。

对于基本值类型,其表现和引用类型又有所不同,编译器并不把初始赋值当作对变量的使用:

int n1 = 0; // 有警告

int n2 = 1234; // 有警告

int? n3 = null; // 有警告

int? n4 = 0; // 有警告

int? n5 = 1234; // 有警告

string从实现上来说应当算是引用类型,但表现上却更加类似于值类型,警告信息也和值类型相同。

对于稍微复杂一些的值类型,结果有点微妙:

DateTime dt1; // 有警告

DateTime dt2 = new DateTime(); // 有警告

DateTime dt3 = new DateTime(2009,1,1); // 没有警告

DateTime dt4 = DateTime.Now; // 没有警告

这个结果有一点是需要注意的。尽管DateTime的默认构造函数和带参构造函数从用户角度看同样是构造函数,但在编译器的角度来看却是不一样的。用IL反编译也可以看出,如果调用默认构造函数的话,那么编译器调用的是initobj指令,而对带参构造函数调用的则是call ctor指令。此外,尽管从程序员的角度来看赋值代码的格式是完全相同的,但编译器却会根据所赋的值不同而采取不同的构造策略,这也是比较违反直觉的。

最后的结论比较遗憾,那就是C#的编译警告并不足以给予程序员足够的保护,特别是对于数组:

byte[] buf = new byte[1024];

如果仅构造这样一个数组而没有使用的话,那么编译器并不会给予程序员任何警告信息。

另外一个问题也是值得考虑的,声明一个类而不使用任何方法,比如仅仅

T t = new T()

这是合理的行为吗?编译器应该为此发出警告吗?

我个人的看法是,从使用的角度来说,这是不合理的,应当尽量避免,编译器发现此用法的话应该提出警告。如果确实有需要的话,可以通过编译指令或Attribute的方法来特别声明来避免警告信息。然而C#编译器的行为却是不发出警告,这一点我是不认同的。当然,我也希望大家提出自己的想法。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
 
© 2005- 王朝網路 版權所有 導航