Одно досадное ограничение языков .net состоит в том, что они не имеют никакого понятия о свойстве, которое делает что-либо, кроме возврата значения, которое затем может быть использовано, если вызывающая сторона сочтет нужным. Было бы очень полезно (и если бы у меня были средства подачи петиций для языковых функций, я бы искал это), если бы существовали стандартные поддерживаемые компилятором средства представления свойств как вызывающих делегатов, например, такой оператор:
MyListOfPoints[4].X = 5;
может быть переведено компилятором во что-то вроде:
MyListOfPoints.ActOnItem(4, (ref Point it) => it.X = 5);
Такой код может быть относительно эффективным и не создавать никакого давления GC, если ActOnItem
принимает дополнительный параметр ref универсального типа и передает его делегату, который также принимает параметр этого типа. Это позволит статически вызывать вызываемую функцию, исключая необходимость создания замыканий или делегатов для каждого выполнения включающей функции. Если бы у ActOnItem
был способ принять переменное число общих параметров 'ref', можно было бы даже обрабатывать такие конструкции, как:
SwapItems(ref MyListOfPoints[4].X, ref MyListofPoints[4].Y);
с произвольными комбинациями параметров 'ref', но даже просто с возможностью обрабатывать случаи, когда свойство было "задействовано" слева от присваивания, или функция была вызвана с единственным свойством-ref 'ref' параметр, было бы полезно.
Обратите внимание, что возможность делать вещи таким образом принесет дополнительную выгоду помимо возможности доступа к полям структур. Это также будет означать, что объект, выставляющий свойство, получит уведомление о том, что с ним покончил потребитель (так как делегат потребителя вернется). Представьте, например, что у вас есть элемент управления, который показывает список элементов, каждый со строкой и цветом, и кто-то хочет иметь возможность сделать что-то вроде:
MyControl.Items(5).Color = Color.Red;
Простой оператор для чтения и самый естественный способ изменить цвет пятого элемента списка, но попытка заставить такой оператор работать, потребует, чтобы объект, возвращаемый Items(5)
, имел ссылку на MyControl
и отправьте уведомление, когда оно изменилось. Скорее сложно. В отличие от этого, если бы поддерживался указанный выше стиль сквозного вызова, такая вещь была бы намного проще. Метод ActOnItem(index, proc, param)
будет знать, что после возвращения proc
ему придется перерисовать элемент, указанный в index
. Немаловажно, если бы Items(5)
был процедурой сквозного вызова и не поддерживал какой-либо метод прямого чтения, можно было бы избежать таких сценариев, как:
var evil = MyControl.Items(5);
MyControl.items.DeleteAt(0);
// Should the following statement affect the item that used to be fifth,
// or the one that's fifth now, or should it throw an exception? How
// should such semantics be ensured?
evil.Color = Color.Purple;
Значение MyControl.Items(5)
останется связанным с MyControl
только на время сквозного вызова с его участием. После этого это будет просто отдельное значение.