Властивість або індексатор не можна передавати як параметр 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);

Все інше працює нормально, і посилання між шарами є.


У bd.Budget bd є об'єктом класу BudgetAllocate. Вибач я забув.
Пратік

можливий дублікат властивості C # та параметра ref, чому немає цукру?
Пітер О.



Щойно виявив, що це працює з типом користувача, у якому були визначені поля, які, як я очікував, DataGridзаповнюватимуться з того часу, щоб дізнатись це лише авто з властивостями. Перехід до властивостей порушив деякі параметри ref, які я використовував у своїх полях. Потрібно визначити локальні змінні для аналізу.
jxramos

Відповіді:


37

ви не можете використовувати

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

замініть bd.Budget якоюсь змінною.

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

11
навіщо використовувати одну зайву змінну ??
Пратік

6
@pratik Ви не можете передати властивість як параметр out, оскільки немає гарантії, що властивість насправді має сеттер, тому вам потрібна додаткова змінна.
матовий

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) посилання на об'єкт посилального типу (або нульове значення), або (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

9

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

double tempVar = 0.0;

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

Оновлення : Прямо від MSDN:

Властивості не є змінними, тому їх не можна передавати як параметри.


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

Це дуже стара публікація, але я поправляю прийняту, бо існує ще більш зручний спосіб зробити це, про що я не знав.

Це називається вбудованою декларацією і, можливо, завжди було доступним (як при використанні операторів), або, можливо, його додавали з C # 6.0 або C # 7.0 для таких випадків, не впевнений, але все одно працює як шарм:

Інедат цього

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

використовуй це:

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

2
Я б використав функцію return, щоб перевірити, чи був синтаксичний аналіз успішним у разі невірного введення.
MarcelDevG

1

Тож бюджет - це властивість, так?

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

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

Дякую. Але чи можу я знати, чому так?
Пратік

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));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.