Я думаю, что вы, возможно, ищете решение, включающее два интерфейса, в которых один наследует от другого:
public interface IReadableFoo
{
IMyValInterface MyVal { get; }
}
public interface IWritableFoo : IReadableFoo
{
IMyValInterface MyVal { set; }
}
public class Foo : IWritableFoo
{
private ConcreteMyVal _myVal;
public IMyValInterface MyVal
{
get { return _myVal; }
set { _myVal = value as ConcreteMyVal; }
}
}
Затем вы можете объявить методы, чей тип параметра «сообщает», планирует ли он изменение переменной:
public void SomeFunction(IReadableFoo fooVar)
{
// Cannot modify fooVar, excellent!
}
public void SomeOtherFunction(IWritableFoo fooVar)
{
// Can modify fooVar, take care!
}
Это имитирует проверки во время компиляции аналогично константности в C ++. Как правильно заметил Эрик Липперт, это не то же самое, что неизменность. Но, как программист C ++, я думаю, вы это знаете.
Кстати, вы можете добиться немного лучшей проверки во время компиляции, если объявите тип свойства в классе как ConcreteMyVal
и реализуете свойства интерфейса отдельно:
public class Foo : IWritableFoo
{
private ConcreteMyVal _myVal;
public ConcreteMyVal MyVal
{
get { return _myVal; }
set { _myVal = value; }
}
public IMyValInterface IReadableFoo.MyVal { get { return MyVal; } }
public IMyValInterface IWritableFoo.MyVal
{
// (or use “(ConcreteMyVal)value” if you want it to throw
set { MyVal = value as ConcreteMyVal; }
}
}
Таким образом, сеттер может выбрасывать только при доступе через интерфейс, но не при доступе через класс.