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

86

Я получаю указанную выше ошибку и не могу ее исправить. Я немного погуглил, но не могу избавиться от этого.

Сценарий:

У меня есть класс BudgetAllocate, свойство которого - бюджет двойного типа.

В моем dataAccessLayer,

На одном из моих занятий я пытаюсь сделать это:

double.TryParse(objReader[i].ToString(), out bd.Budget);

Что вызывает эту ошибку:

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

Я даже пробовал это:

double.TryParse(objReader[i].ToString().Equals(DBNull.Value) ? "" : objReader[i].ToString(), out bd.Budget);

Все остальное работает нормально и ссылки между слоями присутствуют.

Pratik
источник
В bd.Budget bd является объектом класса BudgetAllocate. Извините я забыл.
Pratik
1
Возможный дубликат передачи свойств по ссылке в C #
Леголас
Я только что обнаружил это, работая с типом пользователя, у которого были определены поля, которые, как я ожидал, DataGridбудут заполнять, а затем я узнал, что он только автоматически со свойствами. При переключении на свойства были нарушены некоторые параметры ref, которые я использовал в своих полях. Для синтаксического анализа необходимо определить локальные переменные.
jxramos

Ответы:

37

ты не можешь использовать

double.TryParse(objReader[i].ToString(), out bd.Budget); 

замените bd.Budget какой-нибудь переменной.

double k;
double.TryParse(objReader[i].ToString(), out k); 
Дхинеш
источник
11
зачем использовать одну лишнюю переменную ??
Pratik
6
@pratik Вы не можете передать свойство как выходной параметр, потому что нет гарантии, что у свойства действительно есть установщик, поэтому вам нужна дополнительная переменная.
Мэтт
23
@ mjd79: Ваши рассуждения неверны. Компилятор знает, есть ли сеттер или нет. Предположим, есть сеттер; это должно быть разрешено?
Эрик Липперт
21
@dhinesh, я думаю, ОП ищет ответ о том, почему он не может этого сделать, а не только о том, что он должен делать вместо этого. Прочтите ответ Ганса Пассанта и комментарии Эрика Липперта.
slugster
2
@dhinesh "Настоящая" причина, по которой он не может этого сделать, заключается в том, что он использует C #, а не VB, который ДЕЙСТВИТЕЛЬНО позволяет это. Я из мира VB (очевидно?), И меня часто удивляют дополнительные ограничения, накладываемые C #.
SteveCinq
149

Другие дали вам решение, но почему это необходимо: свойство - это просто синтаксический сахар для метода .

Например, когда вы объявляете свойство, вызываемое Nameс помощью методов получения и установки, под капотом компилятор фактически генерирует методы с именами get_Name()и set_Name(value). Затем, когда вы читаете и записываете в это свойство, компилятор переводит эти операции в вызовы этих сгенерированных методов.

Если учесть это, становится понятно , почему вы не можете передать свойство в качестве выходного параметра - вы фактически передать ссылку на метод , а не ссылки на объект переменной , которая является какая ожидает параметр вывода.

Аналогичный случай существует для индексаторов.

Майк Чемберлен
источник
19
Ваши доводы верны до последнего момента. Параметр out ожидает ссылку на переменную , а не на объект .
Эрик Липперт
@EricLippert, но не переменная, а также объект или что мне не хватает?
meJustAndrew
6
@meJustAndrew: Переменная не является объектом . Переменная - это место хранения . Место хранения содержит либо (1) ссылку на объект ссылочного типа (или null), либо (2) значение объекта типа значения. Не путайте контейнер с тем, что в нем содержится.
Эрик Липперт
6
@meJustAndrew: Рассмотрим объект, скажем, дом. Рассмотрим лист бумаги, на котором написан адрес дома. Представьте ящик, в котором находится этот лист бумаги. Ни ящик, ни бумага - не дом .
Эрик Липперт
69

Это случай дырявой абстракции. Свойство на самом деле является методом, методы доступа get и set для индексатора компилируются в методы get_Index () и set_Index. Компилятор делает потрясающую работу, скрывая этот факт, например, он автоматически переводит присвоение свойству соответствующего метода set_Xxx ().

Но это идет вверх, когда вы передаете параметр метода по ссылке. Это требует, чтобы JIT-компилятор передавал указатель на ячейку памяти переданного аргумента. Проблема в том, что его нет, присвоение значения свойства требует вызова метода установки. Вызываемый метод не может отличить переданную переменную от переданного свойства и, следовательно, не может знать, требуется ли вызов метода.

Примечательно то, что это действительно работает в VB.NET. Например:

Class Example
    Public Property Prop As Integer

    Public Sub Test(ByRef arg As Integer)
        arg = 42
    End Sub

    Public Sub Run()
        Test(Prop)   '' No problem
    End Sub
End Class

Компилятор VB.NET решает эту проблему, автоматически генерируя этот код для метода Run, выраженный на C #:

int temp = Prop;
Test(ref temp);
Prop = temp;

Это обходной путь, который вы также можете использовать. Не совсем понимаю, почему команда C # не использовала тот же подход. Возможно, потому что они не хотели скрывать потенциально дорогие вызовы геттеров и сеттеров. Или совершенно недиагностируемое поведение, которое вы получите, когда у установщика есть побочные эффекты, которые изменяют значение свойства, они исчезнут после назначения. Классическая разница между C # и VB.NET, C # - «без сюрпризов», VB.NET - «заставить работать, если можно».

Ганс Пассан
источник
15
Вы правы, говоря, что не хотите производить дорогостоящие звонки. Вторая причина заключается в том, что семантика «копирование в-копирование» отличается от семантики ссылок, и было бы несовместимо иметь две слегка разные семантики для передачи ссылки. (Тем не менее, есть несколько редких ситуаций, в которых скомпилированные деревья выражений, к сожалению, выполняют копирование-в-копирование.)
Эрик Липперт,
2
Что действительно необходимо, так это большее разнообразие режимов передачи параметров, чтобы компилятор мог заменить «копировать / копировать» там, где это необходимо, но кричать в тех случаях, когда это не так.
supercat 01
9

Поместите параметр out в локальную переменную, а затем установите переменную в bd.Budget:

double tempVar = 0.0;

if (double.TryParse(objReader[i].ToString(), out tempVar))
{
    bd.Budget = tempVar;
}

Обновление : прямо из MSDN:

Свойства не являются переменными и поэтому не могут быть переданы как параметры out.

Адам Хулдсворт
источник
1
@ E.vanderSpoel К счастью, я поднял контент, удалил ссылку.
Адам Хоулдсворт
8

Возможно, интересно - вы можете написать свое:

    //double.TryParse(, out bd.Budget);
    bool result = TryParse(s, value => bd.Budget = value);
}

public bool TryParse(string s, Action<double> setValue)
{
    double value;
    var result =  double.TryParse(s, out value);
    if (result) setValue(value);
    return result;
}
Дэвид Холлинсхед
источник
5

Это очень старый пост, но я исправляю принятый, потому что есть еще более удобный способ сделать это, о котором я не знал.

Это называется встроенным объявлением и может быть всегда доступно (как в операторах using) или может быть добавлено в C # 6.0 или C # 7.0 для таких случаев, не уверен, но в любом случае работает как шарм:

Inetad этого

double temp;
double.TryParse(objReader[i].ToString(), out temp);
bd.Budget = temp;

использовать это:

double.TryParse(objReader[i].ToString(), out double temp);
bd.Budget = temp;
ДанДан
источник
2
Я бы использовал возврат, чтобы проверить, был ли синтаксический анализ успешным в случае недопустимого ввода.
MarcelDevG
1

Итак, бюджет - это собственность, верно?

Скорее сначала установите его в локальную переменную, а затем установите для нее значение свойства.

double t = 0;
double.TryParse(objReader[i].ToString(), out t); 
bd.Budget = t;
Адриан Стандер
источник
Спасибо, но могу ли я узнать почему?
Pratik
0

Обычно, когда я пытаюсь это сделать, я хочу установить свое свойство или оставить его по умолчанию. С помощью этого ответа и dynamicтипов мы можем легко создать метод расширения строки, чтобы он оставался единым и простым.

public static dynamic ParseAny(this string text, Type type)
{
     var converter = TypeDescriptor.GetConverter(type);
     if (converter != null && converter.IsValid(text))
          return converter.ConvertFromString(text);
     else
          return Activator.CreateInstance(type);
}

Используйте так;

bd.Budget = objReader[i].ToString().ParseAny(typeof(double));

// Examples
int intTest = "1234".ParseAny(typeof(int)); // Result: 1234
double doubleTest = "12.34".ParseAny(typeof(double)); // Result: 12.34
decimal pass = "12.34".ParseAny(typeof(decimal)); // Result: 12.34
decimal fail = "abc".ParseAny(typeof(decimal)); // Result: 0
string nullStr = null;
decimal failedNull = nullStr.ParseAny(typeof(decimal)); // Result: 0

По желанию

С другой стороны, если это так, SQLDataReaderвы также можете использовать GetSafeStringрасширения, чтобы избежать нулевых исключений от читателя.

public static string GetSafeString(this SqlDataReader reader, int colIndex)
{
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

public static string GetSafeString(this SqlDataReader reader, string colName)
{
     int colIndex = reader.GetOrdinal(colName);
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

Используйте так;

bd.Budget = objReader.GetSafeString(i).ParseAny(typeof(double));
bd.Budget = objReader.GetSafeString("ColumnName").ParseAny(typeof(double));
Clamchoda
источник