C#中如何实现C++中的const reference

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中没有这样的机制。


  1. 代码契约的编译期井盖检查需要安装一个扩展,详情见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

posted @ 2012-11-17 17:20  HCOONa  阅读(2570)  评论(0编辑  收藏  举报