C#中如何实现C++中的const
reference
在读C# in depth时,作者曾经感慨过,可惜C#中没有类似于C++的const机制,没有办法方便的返回一个对象的只读视图。读到这里,我就对于这一问题耿耿于怀。C++中的const和C#中的readonly有何区别?C++的const好在哪里?为什么C#没有实现C++中的const机制?如何弥补这一缺憾?
C++中的const
与C#中的readonly
C++中的类型系统比C#更为丰富。以C#中的类型系统进行分类。对于值类型对象来说,两者的作用是基本等同的。但是对于引用类型来说,两者有着截然不同的效果。C++中的const
关键字能够保证在不进行显示的类型转换的情况下,被引用的对象内容不会发生改变。而C#中的readonly
关键字,则只能保证该引用本身不被改变,例如变更为引用其他的对象或空引用null
。
C++中的const
的含义非常广,除了可以标记数据类型以外,还可以标记成员函数。被标记为const
的成员函数不能够直接或间接(比如说调用其它非const
的成员函数)的修改当前对象实例的值。
对象的只读视图
在实际开发中,我们经常需要使用某一对象的只读视图。在C++中,假设有这样一个类:
class Counter
{
private:
int x;
public:
Counter(int x) : x(x) {}
const int GetX(void) const { return x; }
void SetX(const int x) { this->x = x; }
void Increase(void) { x++; }
};
如果不看成员函数是否具有const
标记的话,我们很难从表面上知道一些成员函数是否会修改该对象的内容。这个例子肯定大家都能从函数名上判断出来,但是实际中的某些情况并不是这么显然。假设我们知道SetX
这样的方法肯定是会修改对象内容的,但是却不知道Increase
方法也会修改对象内容。如果我们不恰当的使用了Increase
方法,则会酿成一些惨剧。例如:
void LogCounterShouldImmutable(Counter &c)
{
c.Increase();
cout << "Counter value: " << c.GetX() << endl;
}
在C++中,我们可以使用const
修饰符来限制这种不是我们意愿的改变对象内容的调用:
void LogCounterImmutable(const Counter &c)
{
cout << "Counter value: " << c.GetX() << endl;
}
如果在LogCounterImmutable
调用c.Increase();
,编译器会报告一个错误(内容视你使用的编译器而定):
test1.cpp(18) : error C2662: 'Counter::Increase' : cannot convert 'this' pointer from 'const Counter' to 'Counter &' Conversion loses qualifiers
但是在C#中,我们就很难做出这种限制,因为C#语言没有提供类似的内建的机制。例如:
public class ImmutableContainer<T> { private readonly T obj; public ImmutableContainer(T obj) { this.obj = obj; } public T Obj { get { return obj; } } } private void LogCounterImmutable(ImmutableContainer<Counter> container) { Console.WriteLine("Counter value should be {0}.", container.obj.X); container.obj.Increase(); Console.WriteLine("Counter value changed to {0}.", container.obj.X); }
尽管我们想要将Counter
视作一个不可改变的对象,我们还是在无意中改变了其内容。
在C#中如何实现一个对象的只读视图
注意到C++中,实际上是使用了const
关键字标识出了哪些函数是只读的,从而实现了对象的只读视图这样的机制。在C#中没有办法能够这么方便的做到这一点,但是我们仍然可以手工标志出哪些函数是只读的,从而实现对该对象的只读访问:
public interface IReadOnlyCounter { int X { get; } } public class Counter : IReadOnlyCounter { public int X { get; set; } public Counter(int x) { this.X = x; } public void Increase() { X++; } } private void LogCounterImmutable(IReadOnlyCounter counter) { Console.WriteLine("Counter value should be {0}.", counter.X); }
仍然不及C++的const
机制的是,即便是我们在IReadOnlyXXX
接口中标识出的方法,仍然有可能会改变对象的内部状态,不能获得编译时期的静态检查。
好在.NET 4.0中的代码契约改变了这一点:我们可以使用代码契约保证该方法不会改变对象的内部状态,并且还能获得编译时期的静态检查1。
2012/11/18 补充——突然想到应该这样会更好一些(在Const方法中只使用值变量或只读视图中的方法,必要时还可以方便的获得该对象的只读视图):
public interface IReadOnlyCounter { int X { get; } void Log(); } public class Counter : IReadOnlyCounter { public readonly IReadOnlyCounter constThis = (IReadOnlyCounter)this; public int X { get; set; } public Counter(int x) { this.X = x; } public void Increase() { X++; } public void Log() { Console.WriteLine(constThis.X); } } private void LogCounterImmutable(IReadOnlyCounter counter) { Console.WriteLine("Counter value should be {0}.", counter.X); counter.Log(); }
补充材料:为什么C#中没有像C++一样的const
机制
我没有找到官方的材料说明为什么C#中没有这样的机制,但是我找到了官方的材料说明为什么Java中没有这样的机制。