Why is it impossible to override a getter-only property and add a setter?
It's possible.
tl;dr– You can override a get-only method with a setter if you want. It's basically just:
Create a
new
property that has both aget
and aset
using the same name.override
the priorget
to alias the newget
.
This enables us to override properties with get
/set
even if they lacked a setter in their base definition.
Situation: Pre-existing get
-only property.
You have some class structure that you can't modify. Maybe it's just one class, or it's a pre-existing inheritance tree. Whatever the case, you want to add a set
method to a property, but can't.
public abstract class A // Pre-existing class; can't modify
{
public abstract int X { get; } // You want a setter, but can't add it.
}
public class B : A // Pre-existing class; can't modify
{
public override int X { get { return 0; } }
}
Problem: Can't override
the get
-only with get
/set
.
You want to override
with a get
/set
property, but it won't compile.
public class C : B
{
private int _x;
public override int X
{
get { return _x; }
set { _x = value; } // Won't compile
}
}
Solution: Use an abstract
intermediate layer.
While you can't directly override
with a get
/set
property, you can:
Create a
new
get
/set
property with the same name.override
the oldget
method with an accessor to the newget
method to ensure consistency.
So, first you write the abstract
intermediate layer:
public abstract class C : B
{
// Seal off the old getter. From now on, its only job
// is to alias the new getter in the base classes.
public sealed override int X { get { return this.XGetter; } }
protected abstract int XGetter { get; }
}
Then, you write the class that wouldn't compile earlier. It'll compile this time because you're not actually override
'ing the get
-only property; instead, you're replacing it using the new
keyword.
public class D : C
{
private int _x;
public new virtual int X
{
get { return this._x; }
set { this._x = value; }
}
// Ensure base classes (A,B,C) use the new get method.
protected sealed override int XGetter { get { return this.X; } }
}
Result: Everything works!
var d = new D();
var a = d as A;
var b = d as B;
var c = d as C;
Print(a.X); // Prints "0", the default value of an int.
Print(b.X); // Prints "0", the default value of an int.
Print(c.X); // Prints "0", the default value of an int.
Print(d.X); // Prints "0", the default value of an int.
// a.X = 7; // Won't compile: A.X doesn't have a setter.
// b.X = 7; // Won't compile: B.X doesn't have a setter.
// c.X = 7; // Won't compile: C.X doesn't have a setter.
d.X = 7; // Compiles, because D.X does have a setter.
Print(a.X); // Prints "7", because 7 was set through D.X.
Print(b.X); // Prints "7", because 7 was set through D.X.
Print(c.X); // Prints "7", because 7 was set through D.X.
Print(d.X); // Prints "7", because 7 was set through D.X.
Discussion.
This method allows you to add set
methods to get
-only properties. You can also use it to do stuff like:
Change any property into a
get
-only,set
-only, orget
-and-set
property, regardless of what it was in a base class.Change the return type of a method in derived classes.
The main drawbacks are that there's more coding to do and an extra abstract class
in the inheritance tree. This can be a bit annoying with constructors that take parameters because those have to be copy/pasted in the intermediate layer.
Bonus: You can change the property's return-type.
As a bonus, you can also change the return type if you want.
If the base definition was
get
-only, then you can use a more-derived return type.If the base definition was
set
-only, then you can use a less-derived return type.If the base definition was already
get
/set
, then:you can use a more-derived return type if you make it
set
-only;you can use a less-derived return type if you make it
get
-only.
In all cases, you can keep the same return type if you want.
I stumbled across the very same problem today and I think I have a very valid reason for wanting this.
First I'd like to argue that having a get-only property doesn't necessarily translate into read-only. I interpret it as "From this interface/abstract class you can get this value", that doesn't mean that some implementation of that interface/abstract class won't need the user/program to set this value explicitly. Abstract classes serve the purpose of implementing part of the needed functionality. I see absolutely no reason why an inherited class couldn't add a setter without violating any contracts.
The following is a simplified example of what I needed today. I ended up having to add a setter in my interface just to get around this. The reason for adding the setter and not adding, say, a SetProp method is that one particular implementation of the interface used DataContract/DataMember for serialization of Prop, which would have been made needlessly complicated if I had to add another property just for the purpose of serialization.
interface ITest
{
// Other stuff
string Prop { get; }
}
// Implements other stuff
abstract class ATest : ITest
{
abstract public string Prop { get; }
}
// This implementation of ITest needs the user to set the value of Prop
class BTest : ATest
{
string foo = "BTest";
public override string Prop
{
get { return foo; }
set { foo = value; } // Not allowed. 'BTest.Prop.set': cannot override because 'ATest.Prop' does not have an overridable set accessor
}
}
// This implementation of ITest generates the value for Prop itself
class CTest : ATest
{
string foo = "CTest";
public override string Prop
{
get { return foo; }
// set; // Not needed
}
}
I know this is just a "my 2 cents" post, but I feel with the original poster and trying to rationalize that this is a good thing seems odd to me, especially considering that the same limitations doesn't apply when inheriting directly from an interface.
Also the mention about using new instead of override does not apply here, it simply doesn't work and even if it did it wouldn't give you the result wanted, namely a virtual getter as described by the interface.
I think the main reason is simply that the syntax is too explicit for this to work any other way. This code:
public override int MyProperty { get { ... } set { ... } }
is quite explicit that both the get
and the set
are overrides. There is no set
in the base class, so the compiler complains. Just like you can't override a method that's not defined in the base class, you can't override a setter either.
You might say that the compiler should guess your intention and only apply the override to the method that can be overridden (i.e. the getter in this case), but this goes against one of the C# design principles - that the compiler must not guess your intentions, because it may guess wrong without you knowing.
I think the following syntax might do nicely, but as Eric Lippert keeps saying, implementing even a minor feature like this is still a major amount of effort...
public int MyProperty
{
override get { ... } // not valid C#
set { ... }
}
or, for autoimplemented properties,
public int MyProperty { override get; set; } // not valid C#