在读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中没有这样的机制。
-
代码契约的编译期井盖检查需要安装一个扩展,详情见http://blogs.msdn.com/b/bclteam/archive/2010/01/26/i-just-installed-visual-studio-2010-now-how-do-i-get-code-contracts-melitta-andersen.aspx ↩
|
请发表评论