Właściwość lub indeksator nie mogą być przekazywane jako parametr out lub ref

86

Otrzymuję powyższy błąd i nie mogę go rozwiązać. Trochę googlowałem, ale nie mogę się tego pozbyć.

Scenariusz:

Mam klasę BudgetAllocate, której właściwością jest budżet typu double.

W moich dataAccessLayer,

Na jednym z moich zajęć staram się to zrobić:

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

Który generuje ten błąd:

Właściwość lub indeksator nie mogą być przekazywane jako parametr out lub ref w czasie kompilacji.

Próbowałem nawet tego:

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

Wszystko inne działa dobrze i istnieją odniesienia między warstwami.

Pratik
źródło
W bd.Budget bd jest obiektem klasy BudgetAllocate. Przepraszam, zapomniałem.
Pratik
możliwy duplikat właściwości C # i parametru ref, dlaczego nie ma cukru?
Peter O.
Właśnie odkryłem to, pracując z typem użytkownika, który miał zdefiniowane pola, które od tego czasu miałem DataGridwypełnić, nauczyłem się tego tylko automatycznie z właściwościami. Przełączenie na właściwości zepsuło niektóre parametry ref, których używałem na moich polach. Musisz zdefiniować zmienne lokalne, z którymi chcesz przeprowadzić analizę.
jxramos

Odpowiedzi:

37

nie możesz użyć

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

zamień bd.Budget na jakąś zmienną.

double k;
double.TryParse(objReader[i].ToString(), out k); 
dhinesh
źródło
11
po co używać jednej dodatkowej zmiennej?
Pratik
6
@pratik Nie możesz przekazać właściwości jako parametru wyjściowego, ponieważ nie ma gwarancji, że właściwość faktycznie ma metodę ustawiającą, więc potrzebujesz dodatkowej zmiennej.
mat.
23
@ mjd79: Twoje rozumowanie jest nieprawidłowe. Kompilator wie, czy istnieje setter, czy nie. Załóżmy, że był rozgrywający; czy powinno być dozwolone?
Eric Lippert
21
@dhinesh, myślę, że OP szuka odpowiedzi, dlaczego nie może tego zrobić, a nie tylko tego, co musi zrobić. Przeczytaj odpowiedź Hansa Passanta i komentarze Erica Lipperta.
slugster
2
@dhinesh „Prawdziwym” powodem, dla którego nie może tego zrobić, jest to, że używa języka C # zamiast VB, który na to zezwala. Jestem ze świata VB (oczywiście?) I często jestem zaskoczony dodatkowymi ograniczeniami narzucanymi przez C #.
SteveCinq
149

Inni podali ci rozwiązanie, ale dlaczego jest to konieczne: właściwość jest po prostu cukrem syntaktycznym dla metody .

Na przykład, gdy deklarujesz właściwość wywoływaną Nameza pomocą metody pobierającej i ustawiającej, kompilator w rzeczywistości generuje metody o nazwie get_Name()i set_Name(value). Następnie podczas odczytywania i zapisywania w tej właściwości kompilator tłumaczy te operacje na wywołania tych wygenerowanych metod.

Jeśli wziąć pod uwagę to, staje się oczywiste, dlaczego nie można przekazać obiekt jako parametr wyjściowy - czy faktycznie przekazując odwołanie do metody , zamiast odniesienia do obiektu zmiennej , która jest, co wyjście parametrów oczekuje.

Podobny przypadek istnieje w przypadku indeksatorów.

Mike Chamberlain
źródło
19
Twoje rozumowanie jest poprawne aż do ostatniego kawałka. Parametr out oczekuje odniesienia do zmiennej , a nie do obiektu .
Eric Lippert
@EricLippert, ale czy zmienna nie jest również obiektem, czy czego mi brakuje?
meJustAndrew
6
@meJustAndrew: Zmienna absolutnie nie jest obiektem . Zmienna to miejsce przechowywania . Miejsce przechowywania zawiera albo (1) odniesienie do obiektu typu referencyjnego (lub null), albo (2) wartość obiektu typu wartości. Nie pomyl pojemnika z zawartością w nim rzeczy.
Eric Lippert
6
@meJustAndrew: Rozważmy obiekt, powiedzmy, dom. Rozważmy kartkę papieru, na której jest napisany adres domu. Rozważ szufladę, w której znajduje się ten kawałek papieru. Ani szuflada, ani papier nie są domem .
Eric Lippert
69

To jest przypadek nieszczelnej abstrakcji. Właściwość jest w rzeczywistości metodą, metody dostępu get i set dla indeksatora są kompilowane do metod get_Index () i set_Index. Kompilator wykonuje świetną robotę ukrywając ten fakt, automatycznie tłumaczy przypisanie do właściwości na przykład na odpowiednią metodę set_Xxx ().

Ale to idzie do góry nogami, gdy przekażesz parametr metody przez odniesienie. Wymaga to od kompilatora JIT przekazania wskaźnika do lokalizacji pamięci przekazanego argumentu. Problem w tym, że takiego nie ma, przypisanie wartości właściwości wymaga wywołania metody ustawiającej. Wywołana metoda nie może odróżnić przekazanej zmiennej od przekazanej właściwości i dlatego nie może wiedzieć, czy wywołanie metody jest wymagane.

Godne uwagi jest to, że to faktycznie działa w VB.NET. Na przykład:

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

Kompilator VB.NET rozwiązuje ten problem, automatycznie generując ten kod dla metody Run, wyrażony w języku C #:

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

Które obejście można również zastosować. Nie do końca wiem, dlaczego zespół C # nie zastosował tego samego podejścia. Prawdopodobnie dlatego, że nie chcieli ukrywać potencjalnie kosztownych wywołań getter i setter. Lub całkowicie niezdiagnozowalne zachowanie, które otrzymasz, gdy ustawiacz ma efekty uboczne, które zmieniają wartość właściwości, znikną po przypisaniu. Klasyczna różnica między C # a VB.NET, C # to „bez niespodzianek”, a VB.NET to „zrób to, jeśli możesz”.

Hans Passant
źródło
15
Masz rację, mówiąc, że nie chcesz generować drogich połączeń. Drugorzędnym powodem jest to, że semantyka kopiowania-na-kopiowania ma inną semantykę niż semantyka referencji i byłoby niespójne posiadanie dwóch subtelnie różnych semantyki do przekazywania referencji. (To powiedziawszy, zdarzają się rzadkie sytuacje, w których skompilowane drzewa wyrażeń niestety kopiują-na-kopiują).
Eric Lippert,
2
To, co jest naprawdę potrzebne, to większa różnorodność trybów przekazywania parametrów, aby kompilator mógł zastąpić „kopiuj do / kopiuj na zewnątrz” tam, gdzie jest to właściwe, ale wykrzyknąć w przypadkach, gdy tak nie jest.
supercat
9

Umieść parametr out w zmiennej lokalnej, a następnie ustaw zmienną na bd.Budget:

double tempVar = 0.0;

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

Aktualizacja : bezpośrednio z MSDN:

Właściwości nie są zmiennymi i dlatego nie mogą być przekazywane jako parametry wyjściowe.

Adam Houldsworth
źródło
1
@ E.vanderSpoel Na szczęście usunąłem zawartość, usunąłem link.
Adam Houldsworth
8

Ewentualnie interesujące - możesz napisać własne:

    //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;
}
David Hollinshead
źródło
5

To jest bardzo stary post, ale poprawiam zaakceptowany, ponieważ istnieje jeszcze wygodniejszy sposób na zrobienie tego, którego nie znałem.

Nazywa się to deklaracją inline i mogło być zawsze dostępne (jak w przypadku używania instrukcji) lub mogło zostać dodane z C # 6.0 lub C # 7.0 w takich przypadkach, nie jestem pewien, ale i tak działa jak urok:

Poza tym

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

Użyj tego:

double.TryParse(objReader[i].ToString(), out double temp);
bd.Budget = temp;
DanDan
źródło
2
Użyłbym zwrotu, aby sprawdzić, czy analiza zakończyła się pomyślnie w przypadku nieprawidłowych danych wejściowych.
MarcelDevG
1

Więc budżet to własność, prawda?

Zamiast tego najpierw ustaw ją na zmienną lokalną, a następnie ustaw na nią wartość właściwości.

double t = 0;
double.TryParse(objReader[i].ToString(), out t); 
bd.Budget = t;
Adriaan Stander
źródło
Dzięki, ale czy mogę wiedzieć, dlaczego tak jest?
Pratik
0

Zwykle, gdy próbuję to zrobić, dzieje się tak, ponieważ chcę ustawić swoją właściwość lub pozostawić ją jako wartość domyślną. Z pomocą tej odpowiedzi i dynamictypów możemy łatwo stworzyć metodę przedłużania ciągów, aby była jednolita i prosta.

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);
}

Użyj w ten sposób;

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

Opcjonalny

Na marginesie, jeśli tak jest SQLDataReader, możesz również użyć GetSafeStringrozszerzenia (rozszerzeń), aby uniknąć zerowych wyjątków od czytnika.

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;
}

Użyj w ten sposób;

bd.Budget = objReader.GetSafeString(i).ParseAny(typeof(double));
bd.Budget = objReader.GetSafeString("ColumnName").ParseAny(typeof(double));
clamchoda
źródło