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.
Collapse
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 can not 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?
Collapse
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.