Introduction
In C++, one can use the const
keyword to prevent a called function from modifying a parameter that is passed by reference. C# does not allow use of const
in this manner. This article explains how to emulate that functionality through the use of an interface.
Using the Code
First, an interface is created that exposes only read-only properties and/or methods. This interface is then implemented by the class to be protected.
public interface IMyClassReadOnly
{
int A { get; }
}
public class MyClass : IMyClassReadOnly
{
private int a;
public int A
{
get { return a; }
set { a = value; }
}
public MyClass(int a)
{
this.a = a;
}
}
public class Program
{
static void Main(string[] args)
{
MyClass myclass = new MyClass(0);
IMyClassReadOnly myclassReadOnly = myclass as IMyClassReadOnly;
DoNotModify(myclass);
Modify(myclass);
DoNotModify(myclassReadOnly);
}
private static void DoNotModify(IMyClassReadOnly c)
{
Console.WriteLine("DoNotModify: {0}", c.A);
}
private static void Modify(MyClass c)
{
c.A = 42;
}
}
That worked as most people would expect. The modify function is prevented from writing to the value of A
, with a compile time error, because it cannot convert from the interface to the class type.
What would happen if A
were a reference type, as S
is in the following code, instead?
public class SomeOtherClass
{
private int b;
public int B
{
get { return b; }
set { b = value; }
}
public SomeOtherClass(int b)
{
this.b = b;
}
}
public interface IMyClassReadOnly
{
int A { get; }
SomeOtherClass S { get; }
}
public class MyClass : IMyClassReadOnly
{
private int a;
private SomeOtherClass s;
public int A
{
get { return a; }
set { a = value; }
}
public SomeOtherClass S
{
get { return s; }
set { s = value; }
}
public MyClass(int a)
{
this.a = a;
s = new SomeOtherClass(a);
}
}
public class Program
{
static void Main(string[] args)
{
MyClass myclass = new MyClass(0);
IMyClassReadOnly myclassReadOnly = myclass as IMyClassReadOnly;
myclass.S = new SomeOtherClass(1);
myclassReadOnly.S = new SomeOtherClass(2);
myclassReadOnly.S.B = 3;
}
}
The third case is allowed because SomeOtherClass
exposes get
and set
through a property. A simple fix right? You just need to add a read-only interface as was done for MyClass
. Unfortunately, the following doesn't work:
public interface ISomeOtherClassReadOnly
{
int B { get; }
}
public class SomeOtherClass : ISomeOtherClassReadOnly
{
}
That doesn't work because the property in MyClass
returns SomeOtherClass
instead of ISomeOtherClassReadOnly
. This could be remedied by adding another property to the interface and class such as:
public ISomeOtherClassReadOnly SReadOnly
{
get { return s as ISomeOtherClassReadOnly; }
}
However, now usage of the object would differ when accessing the SomeOtherObject
member. Instead, one can modify the IMyClassReadOnly
interface to read:
public interface IMyClassReadOnly
{
int A { get; }
ISomeOtherClassReadOnly S { get; }
}
Now the example won't compile because there is a conflict between the property S
as defined in the class and the interface. How can this be resolved? Simple, implement the interface version explicitly, as follows:
public class MyClass : IMyClassReadOnly
{
ISomeOtherClassReadOnly IMyClassReadOnly.S
{
get { return s as ISomeOtherClassReadOnly; }
}
}
Now, if the third case is retested, it will fail at compile time.
public class Program
{
static void Main(string[] args)
{
MyClass myclass = new MyClass(0);
IMyClassReadOnly myclassReadOnly = myclass as IMyClassReadOnly;
myclassReadOnly.S.B = 3;
}
}
Note that this method of protecting constness is not infallible. Obviously, the interface could be cast back to the class, but the same can be done in C++ with const_cast
.
History
- 6th March, 2008: Initial post