使用Using的注意事项
参数传递
C#中有四种参数类型:值类型,Ref参数,Out参数,params参数。默认参数都是以传值方式传递,这意味着方法中的变量会在内存中被分配新的存储空间,并赋值。对于引用类型,这种传值意味着传递的是实例对象在栈中的地址。
void Foo (StringBuilder x) { x = null; } ... StringBuilder y = new StringBuilder(); y.Append ("hello"); Foo (y); Console.WriteLine (y==null);
代码中的y不会被改变。反之,下面的代码会输出“hello world”。(注意上面赋值的概念,如果变量存在一组子成员变量,现在两个变量都指向了同一片子成员变量,那么子成员变量的变化就会影响到两个变量)
void Foo (StringBuilder x) { x.Append (" world"); } ... StringBuilder y = new StringBuilder(); y.Append ("hello"); Foo (y); Console.WriteLine (y);
如果将参数传递方式修改为Ref,那么上面的y就会被置为null,因为传递的是变量本身而非地址。进入方法的范围时,不会再分配新的存储空间,引用被修改,栈中的地址不再指向已分配的空间。
关于Using
使用Using语句块时,可以再Using语句内定义变量,也可以在其外部定义变量。但是如下这种写法在C#中会报错:Cannot pass 'TheInt' as a ref or out argument because it is a 'using variable'
。
public class DisposableInt : IDisposable { private int? _Value; public int? MyInt { get { return _Value; } set { _Value = value; } } public DisposableInt(int InitialValue) { _Value = InitialValue; } public void Dispose() { _Value = null; } } public class TestAnInt { private void AddOne(ref DisposableInt IntVal) { IntVal.MyInt++; } public void TestIt() { DisposableInt TheInt; using (TheInt = new DisposableInt(1)) { Console.WriteLine(String.Format("Int Value: {0}", TheInt.MyInt)); AddOne(ref TheInt); Console.WriteLine(String.Format("Int Value + 1: {0}", TheInt.MyInt)); } } }
在Using语句内声明的变量全部是只读变量,这意味着在Using的语句块内不允许更改该对象的引用关系(例如,尝试赋值为null会报错)。因此,如果在此范围内使用ref或者out时会抛出编译器错误。在VB.NET中,不会直接抛出错误,但是会出现TheInt不会被修改的现象。原因在于以上的代码会被编译为(部分代码省略):
public void TestIt() { DisposableInt TheInt =new DisposableInt(1); DisposableInt TheInt2 = TheInt; try { AddOne(ref TheInt2 ); } finally { } }
看到一个新的对象代替目标对象进入方法,原有的对象不会被修改。如果在AddOne的范围内出现引用被修改的情况,TheInt和TheInt2的参照关系会被修改。此时,在AddOne之后使用的TheInt实际上已经被编译器自动优化为TheInt2。
为了使用Using,可以讲代码修改为如下,此时TheInt不再是只读变量,就不会遇到此类问题:
DisposableInt theInt = new DisposableInt(1); using (theInt) { AddOne(ref theInt); }