Свойство 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

Другие вопросы по тегам