Свойство C# и параметр ref, почему нет сахара?
Я только что наткнулся на это сообщение об ошибке во время работы в C#
Свойство или индексатор нельзя передавать как параметр out или ref
Я знал, что вызвало это, и быстро решил создать локальную переменную правильного типа, вызвав функцию с ней в качестве out
/ref
параметр, а затем присваивая его обратно свойству:
RefFn(ref obj.prop);
превращается в
{
var t = obj.prop;
RefFn(ref t);
obj.prop = t;
}
Очевидно, что это не получится, если свойство не поддерживает get и set в текущем контексте.
Почему C# не делает это для меня?
Единственные случаи, когда я могу думать о том, где это может вызвать проблемы:
- нарезания резьбы
- исключения
Для многопоточности это преобразование влияет на то, когда происходит запись (после вызова функции и в вызове функции), но я скорее подозреваю, что любой код, который рассчитывает на это, получит небольшое сочувствие, когда он прервется.
За исключением, беспокойство будет; что произойдет, если функция назначит один из нескольких ref
параметры чем кидает? Любое тривиальное решение приведет к тому, что все или ни один из параметров не будет назначен, когда некоторые должны быть, а некоторые - нет. Опять же, я не думаю, что это будет поддерживаться использованием языка.
Примечание: я понимаю механизм того, почему это сообщение об ошибке генерируется. Что я ищу, так это обоснование того, почему C# не выполняет автоматически тривиальный обходной путь.
9 ответов
Просто для информации, C# 4.0 будет иметь что-то вроде этого сахара, но только при вызове методов взаимодействия - частично из-за явной склонности ref
в этом сценарии. Я не проверял это много (в ОСАГО); мы должны увидеть, как это получится...
Потому что вы передаете результат индексатора, который действительно является результатом вызова метода. Нет никакой гарантии, что свойство indexer также имеет установщик, и передача его по ref приведет к ложной защите со стороны разработчика, когда он думает, что его свойство будет установлено без вызова установщика.
На более техническом уровне ref и out передают адрес памяти переданного в них объекта, и для установки свойства необходимо вызвать установщик, поэтому нет гарантии, что свойство действительно будет изменено, особенно когда тип свойства неизменный. ref и out не просто устанавливают значение по возвращении метода, они передают фактическую ссылку на память самому объекту.
Свойства являются не чем иным, как синтаксическим сахаром над методами getX/setX в стиле Java. Это не имеет большого смысла для 'ref' в методе. В вашем случае это будет иметь смысл, потому что ваши свойства просто заглушают поля. Свойства не должны быть просто заглушками, следовательно, фреймворк не может разрешить 'ref' в свойствах.
РЕДАКТИРОВАТЬ: Ну, простой ответ заключается в том, что простой факт, что получатель или установщик свойства может включать в себя гораздо больше, чем просто поле чтения / записи, делает нежелательным, не говоря уже о, возможно, неожиданным, то, что вы предлагаете тот тип сахара, который вы предлагаете. Это не значит, что я раньше не нуждался в этой функции, просто я понимаю, почему они не захотят ее предоставлять.
Вы можете использовать поля с ref
/out
, но не свойства. Причина в том, что свойства - это всего лишь краткий синтаксис для специальных методов. Компилятор фактически переводит свойства get / set в соответствующие get_X
а также set_X
методы, так как CLR не имеет немедленной поддержки свойств.
Это не будет потокобезопасным; если два потока одновременно создают свои собственные копии значения свойства и передают их функциям в качестве параметров ref, только один из них попадает обратно в свойство.
class Program
{
static int PropertyX { get; set; }
static void Main()
{
PropertyX = 0;
// Sugared from:
// WaitCallback w = (o) => WaitAndIncrement(500, ref PropertyX);
WaitCallback w = (o) => {
int x1 = PropertyX;
WaitAndIncrement(500, ref x1);
PropertyX = x1;
};
// end sugar
ThreadPool.QueueUserWorkItem(w);
// Sugared from:
// WaitAndIncrement(1000, ref PropertyX);
int x2 = PropertyX;
WaitAndIncrement(1000, ref x2);
PropertyX = x2;
// end sugar
Console.WriteLine(PropertyX);
}
static void WaitAndIncrement(int wait, ref int i)
{
Thread.Sleep(wait);
i++;
}
}
PropertyX заканчивается как 1, тогда как поле или локальная переменная будет 2.
Этот пример кода также подчеркивает трудности, связанные с такими вещами, как анонимные методы, когда просит компилятор делать что-то нехорошее.
Причина этого заключается в том, что C# не поддерживает "параметрические" свойства, которые принимают параметры, переданные по ссылке. Интересно отметить, что CLR поддерживает эту функциональность, а C# - нет.
Когда вы передаете ref/out prepended, это означает, что вы передаете ссылочный тип, который хранится в куче.
Свойства - это методы-оболочки, а не переменные.
Если вы спрашиваете, почему компилятор не заменяет поле, возвращаемое геттером свойства, это потому, что геттер может возвращать const или readonly, или литерал, или что-то еще, что не следует повторно инициализировать или перезаписывать.
Этот сайт, кажется, имеет обходной путь для вас. Я не проверял это, хотя, поэтому я не могу гарантировать, что это будет работать. Похоже, что в примере используется отражение, чтобы получить доступ к функциям get и set свойства. Это, вероятно, не рекомендуемый подход, но он может выполнить то, что вы просите.
http://www.codeproject.com/KB/cs/Passing_Properties_byref.aspx