Есть вот такой синтаксический сахар, как авто-свойство, когда за кулисами при компиляции создается поле.
Так вот, можно ли на автосвойство повесить триггер, что бы не вызывать рекурсию без создания поля явно?
Ну например, хочу, что бы при присвоении значения меньше 0, оно заменялось на 0.
Нет, так сделать нельзя.
Согласно спецификации автосвойствами являются свойства у которых и в геттере и в сеттере тело состоит из одной точки с запятой, и, соответственно, нет логики. Неявное поле создается только для автосвойств:
An automatically implemented property (or auto-property for short), is a non-abstract non-extern property with semicolon-only accessor bodies. Auto-properties must have a get accessor and can optionally have a set accessor.
When a property is specified as an automatically implemented property, a hidden backing field is automatically available for the property, ...
Т.е. если у свойства есть логика, то у него уже не будет автоматически созданного поля, соответственно, негде будет хранить значения.
Рекурсия в данном случае тоже не поможет, без поля эта рекурсия будет бесконечной:
int backingField;
public int BadProperty
{
get { return backingField; }
set
{
//с полем рекурсия бесссмыслена, но возможна
if (value < 0) BadProperty = 0;
else backingField = value;
}
}
public int ImpossibleProperty
{
set
{
//без поля непонятно что делать
if (value < 0) ImpossibleProperty = 0;
//куда присваивать значение???
}
}
Альтернативы
хочу, что бы при присвоении значения меньше 0, оно заменялось на 0.
Можно смошенничать и формально добиться нужного поведения используя для свойства свою структуру и написать для нее свое приведение типов с проверкой значения. Например, так:
//структура
struct NonNegativeValue
{
private readonly int value;
private NonNegativeValue(int value)
{
this.value = Math.Max(value, 0);
}
public static implicit operator NonNegativeValue(int value)
{
return new NonNegativeValue(value);
}
public static implicit operator int(NonNegativeValue obj)
{
return obj.value;
}
}
internal class MyClass
{
//свойство
public NonNegativeValue Value { get; set; }
}
В результате получили громоздкое решение, к тому же каждое значение свойства оборачивается в структуру. Но цель выполнена:
var obj = new MyClass();
Console.WriteLine(obj.Value); //0
obj.Value = -1;
Console.WriteLine(obj.Value); //0
obj.Value = 5;
Console.WriteLine(obj.Value); //5
Возможно есть другие варианты реализации: через обработку атрибутов, либо через генераторы кода. Сомневаюсь, впрочем, что какой-нибудь из этих вариант будет проще чем свойство с полем:
private int value;
public int Value
{
get { return value; }
set { this.value = value < 0 ? 0 : value; }
}
Как указали в комментариях, такое можно сделать с помощью сторонних AOP-фреймворков, например с помощью PostSharp
Здесь находится документация по написанию аспектов: ссылка
Подключите к проекту пакет PostSharp и напишите такой аспект:
[PSerializable]
class ValueCorrectorAttribute : LocationInterceptionAspect
{
public override void OnSetValue(LocationInterceptionArgs args)
{
if ((int)args.Value < 0)
args.Value = 0;
args.ProceedSetValue();
}
}
Используем:
class A
{
[ValueCorrector]
public int Prop { get; set; }
}
Тестируем:
var a = new A { Prop = 10 };
Console.WriteLine(a.Prop); // 10
a.Prop = -10;
Console.WriteLine(a.Prop); // 0
При желании можете добавить параметры к аспекту и получить чуть более универсальное решение.
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости