Свойство С# и параметр ref, почему нет сахара?

Я только что наткнулся на это сообщение об ошибке, работая на С#

Свойство или индексатор нельзя передавать в качестве параметра out или ref.

Я знал, что вызвало это, и нашел быстрое решение, создав локальную переменную правильного типа, вызвав функцию с ней в качестве параметра out/ref, а затем присвоив ее обратно свойству:

RefFn(ref obj.prop);

превращается в

{
    var t = obj.prop;
    RefFn(ref t);
    obj.prop = t;
}

Ясно, что это потерпит неудачу, если свойство не поддерживает получение и установку в текущем контексте.

Почему C# просто не делает это за меня?


Единственные случаи, когда я могу думать о том, где это может вызвать проблемы:

  • заправка
  • исключения

Для многопоточности это преобразование влияет на то, когда происходит запись (после вызова функции, а не при вызове функции), но я скорее подозреваю, что любой код, который рассчитывает на это, получит мало сочувствия, когда он сломается.

В качестве исключений проблема будет заключаться в следующем; что произойдет, если функция присвоит один из нескольких параметров ref, а не выбросит? Любое тривиальное решение приведет к тому, что все или ни один из параметров не будет назначен, когда некоторые должны быть, а некоторые не должны быть. Опять же, я не думаю, что это будет поддерживаться использованием языка.


Примечание. Я понимаю, почему генерируются эти сообщения об ошибках. Я ищу объяснение того, почему С# автоматически не реализует тривиальное обходное решение.


person BCS    schedule 09.02.2009    source источник
comment
Благими намерениями вымощена дорога к слащавым компиляторам.   -  person BC.    schedule 09.02.2009
comment
Это не делает обходной путь, потому что это не всегда безопасно. Более фундаментальная проблема заключается в том, что свойство типа T должно иметь не только методы получения и установки, но и метод ActUpon<U>([indexparams...,] ActionByRef<T,U> act, ref U param). Такой метод позволил бы оператору типа ListOfPoints[4].X += something эффективно выполняться с использованием статического делегата и без замыканий (ListOfPoints.ActUpon(4, (ref Point it, ref param) => {it.X += param;}, ref something);).   -  person supercat    schedule 26.06.2012
comment
Надежный способ решить эту проблему с помощью выражений внизу этого ответа: stackoverflow.com/a/3059448/176877   -  person Chris Moschini    schedule 04.11.2013


Ответы (9)


Просто для информации: C# 4.0 будет иметь что-то подобное этому сахару, но только при вызове методов взаимодействия — отчасти из-за явной склонности ref в этом сценарии. Я особо не проверял (в CTP); мы должны увидеть, как это сработает...

person Marc Gravell    schedule 09.02.2009
comment
Страница фьючерсов MS C#: code.msdn.microsoft.com /Проект/Загрузить/ - person Marc Gravell; 10.02.2009
comment
специально для COM-методов компилятор C# позволит вам передавать аргументы по значению такому методу и будет автоматически генерировать временные переменные для хранения переданных значений, впоследствии отбрасывая их при возврате вызова. - person Marc Gravell; 10.02.2009
comment
C# не является языком низкого уровня, поэтому параметр ref должен быть не адресом, а парой замыканий, геттером и сеттером. Если вы передаете локальную переменную в качестве параметра ref, язык должен автоматически создать пару геттер/сеттер. И все это должно быть скрыто от ничего не подозревающего программиста. - person pyon; 29.06.2011
comment
@Eduardo, который меняет семантику, ломает пути, особенно для типов значений; это никогда не произойдет. я бы не хотел - person Marc Gravell; 29.06.2011
comment
@Marc: Как это изменит семантику чего-либо? В конце концов, переменная — это всего лишь механизм чтения (получения) и записи (установки) в кучу памяти. - person pyon; 29.06.2011
comment
@ Эдуардо нет; в случае типов значений это является данными. Аксессор (свойство) принимает копию). Что является проблемой в случае изменяемых структур. Теперь вы можете возразить, что у нас не должно быть изменяемых структур... но они есть. - person Marc Gravell; 29.06.2011

Потому что вы передаете результат индексатора, который на самом деле является результатом вызова метода. Нет никакой гарантии, что у свойства индексатора также есть установщик, и передача его по ссылке приведет к ложной безопасности со стороны разработчика, когда он думает, что его свойство будет установлено без вызова установщика.

На более техническом уровне ref и out передают адрес памяти переданного им объекта, а чтобы установить свойство, вы должны вызвать сеттер, поэтому нет никакой гарантии, что свойство действительно будет изменено, особенно когда тип свойства неизменный. ref и out не просто устанавливают значение после возврата метода, они передают реальную ссылку на память самому объекту.

person David Morton    schedule 09.02.2009
comment
+1, важно отметить, что P/Invoke может задохнуться, поскольку объект, членом которого является свойство, больше не может иметь корень GC и не может быть собран! Дох... - person user7116; 10.02.2009
comment
@sixletter: не так, чтобы объект был собран, ссылка на него в вызывающем коде должна быть удалена. - person BCS; 10.02.2009
comment
Описанная вами проблема разрешится сама собой, поскольку сахар во время компиляции попытается разрешить установщик и потерпит неудачу с той же ошибкой, что и попытка присвоить свойство только для получения. - person BCS; 10.02.2009
comment
Да, но имейте в виду, что свойство C# само по себе является синтаксическим сахаром. Люди уже запутались, потому что свойство выглядит точно так же, как поле. Добавление еще большего количества синтаксического сахара наверняка не решит эту проблему. Ограничение есть и должно быть. - person David Morton; 10.02.2009
comment
Если свойство является строкой, результатом свойства является указатель на эту строку. Ссылка — это указатель на этот указатель. Поэтому не имеет значения, является ли это свойством или нет, оно должно исправить ссылку. Это исправление выполнено и отлично работает в VB.net, а C#, похоже, отсутствует. И вы правы, при передаче в качестве ref сеттер вызывается, как и должно быть, и нет никакой гарантии, что свойство действительно будет изменено. Что гарантируется, так это то, что ваш сеттер-код будет работать. Это также можно проверить для работы в VB.NET. - person Brain2000; 03.05.2013

Свойства — это не что иное, как синтаксический сахар над методами getX/setX в стиле Java. Не имеет особого смысла использовать ссылку на метод. В вашем случае это имело бы смысл, потому что ваши свойства просто заглушают поля. Свойства не обязательно должны быть просто заглушками, поэтому фреймворк не может разрешить «ссылку» на свойства.

РЕДАКТИРОВАТЬ: ну, простой ответ заключается в том, что сам факт того, что метод получения или установки свойства может включать в себя гораздо больше, чем просто чтение/запись поля, делает нежелательным, не говоря уже о том, что, возможно, неожиданным, разрешить сортировку сахара вы предлагаете. Это не значит, что я не нуждался в этой функциональности раньше, просто я понимаю, почему они не хотят ее предоставлять.

person user7116    schedule 09.02.2009
comment
+1 Это единственное объяснение, которое не нужно читать 5 раз, чтобы понять. - person goku_da_master; 19.11.2013

Вы можете использовать поля с ref/out, но не свойства. Причина в том, что свойства — это всего лишь сокращение синтаксиса для специальных методов. Компилятор фактически переводит свойства get/set в соответствующие методы get_X и set_X, поскольку среда CLR не имеет непосредственной поддержки свойств.

person Brian Rasmussen    schedule 09.02.2009
comment
Я это знаю, смотрите мою новую заметку. - person BCS; 10.02.2009

Это не было бы потокобезопасным; если два потока одновременно создают свои собственные копии значения свойства и передают их функциям в качестве параметров 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.

Этот пример кода также подчеркивает трудности, связанные с такими вещами, как анонимные методы, когда вы просите компилятор сделать что-то слащавое.

person Mark Rendle    schedule 09.02.2009
comment
Хороший момент, но я думаю, что вы показали, что преобразование может изменить результат кода, который уже имеет условия гонки, например. не потокобезопасный код. - person BCS; 10.02.2009
comment
Это правда, в примере, который я привел. Упс. - person Mark Rendle; 10.02.2009
comment
Я не согласен с вами. Приведенный выше пример просто показывает, что x1 и x2 оба установлены на 0, а затем оба увеличиваются до 1 после периода 500 и 1000 миллисекунд, а затем они оба устанавливают PropertyX на 1. Ни одна локальная переменная никогда не бывает 2. Вы необходимо передать PropertyX в функцию WaitAndIncrement(), чтобы действительно проверить это, что можно сделать только в VB.NET. Однако это, вероятно, все равно не сработает, потому что геттеры вызываются первыми. Это не имеет ничего общего с потокобезопасностью. Это не менее потокобезопасно, чем использование локальной переменной без каких-либо взаимосвязанных функций. - person Brain2000; 03.05.2013
comment
Прочтите мой ответ еще раз, более внимательно, и вы обнаружите, что совершенно со мной не согласны. - person Mark Rendle; 05.05.2013

Причина этого в том, что C# не поддерживает "параметрические" свойства, которые принимают параметры, передаваемые по ссылке. Интересно отметить, что CLR поддерживает эту функцию, а C# — нет.

person Andrew Hare    schedule 09.02.2009
comment
VB.net поддерживает его, я озадачен, почему C# этого не делает. - person Brain2000; 03.05.2013
comment
Может ли кто-нибудь ответить на это, если выше это правда? - person Coops; 27.06.2013

Когда вы передаете ref/out в начале, это означает, что вы передаете ссылочный тип, который хранится в куче.

Свойства — это методы-оболочки, а не переменные.

person Igor Zelaya    schedule 09.02.2009

Если вы спрашиваете, почему компилятор не заменяет поле, возвращаемое получателем свойства, то это потому, что получатель может возвращать константу, только для чтения, литерал или что-то еще, что не следует повторно инициализировать или перезаписывать.

person BC.    schedule 09.02.2009

Этот сайт, кажется, имеет обходной путь для вас. Однако я не проверял его, поэтому не могу гарантировать, что он будет работать. Похоже, что в примере используется отражение, чтобы получить доступ к функциям get и set свойства. Это, вероятно, не рекомендуемый подход, но он может выполнить то, о чем вы просите.

http://www.codeproject.com/KB/cs/Passing_Properties_byref.aspx

person regex    schedule 09.02.2009
comment
Ах, нет ничего плохого в использовании отражения. Иногда это просто исправление для вас. - person Camilo Martin; 19.05.2012