Używam właściwości zaimplementowanych automatycznie. Wydaje mi się, że najszybszym sposobem rozwiązania tego problemu jest zadeklarowanie własnej zmiennej bazowej?
public Point Origin { get; set; }
Origin.X = 10; // fails with CS1612
Komunikat o błędzie: Nie można zmodyfikować wartości zwracanej „wyrażenie”, ponieważ nie jest to zmienna
Podjęto próbę zmodyfikowania typu wartości, który był wynikiem wyrażenia pośredniego. Ponieważ wartość nie jest utrwalana, pozostanie niezmieniona.
Aby rozwiązać ten błąd, zapisz wynik wyrażenia w wartości pośredniej lub użyj typu odwołania dla wyrażenia pośredniego.
c#
variables
struct
immutability
Paweł
źródło
źródło
Odpowiedzi:
Dzieje się tak, ponieważ
Point
jest to typ wartości (struct
).Z tego powodu, gdy uzyskujesz dostęp do
Origin
właściwości, uzyskujesz dostęp do kopii wartości przechowywanej przez klasę, a nie do samej wartości, jak w przypadku typu referencyjnego (class
), więc jeśli ustawisz na niejX
właściwość, to ustawiasz właściwość na kopii, a następnie odrzucenie jej, pozostawiając pierwotną wartość niezmienioną. Prawdopodobnie nie jest to zamierzone przez Ciebie rozwiązanie, dlatego kompilator ostrzega Cię o tym.Jeśli chcesz zmienić tylko
X
wartość, musisz zrobić coś takiego:źródło
Używanie zmiennej bazowej nie pomoże.Point
Typ to typ wartości.Musisz przypisać całą wartość Point do właściwości Origin: -
Problem polega na tym, że kiedy uzyskujesz dostęp do właściwości Origin, to, co jest zwracane przez,
get
jest kopią struktury Point w automatycznie utworzonym polu Właściwości Origin. W związku z tym modyfikacja pola X ta kopia nie wpłynie na pole bazowe. Kompilator wykrywa to i wyświetla błąd, ponieważ ta operacja jest całkowicie bezużyteczna.Nawet jeśli użyłeś własnej zmiennej
get
bazowej, wyglądałoby to tak: -Nadal zwracałbyś kopię struktury Point i otrzymywałbyś ten sam błąd.
Hmm ... po dokładniejszym przeczytaniu pytania, być może faktycznie masz na myśli zmodyfikowanie zmiennej bazowej bezpośrednio z poziomu swojej klasy: -
Tak, to byłoby to, czego potrzebujesz.
źródło
Teraz już wiesz, jakie jest źródło błędu. W przypadku, gdy nie istnieje konstruktor z przeciążeniem, aby przejąć twoją właściwość (w tym przypadku
X
), możesz użyć inicjatora obiektu (który wykona całą magię za kulisami). Nie chodzi o to, że nie musisz uczynić swoich struktur niezmiennymi , ale po prostu podać dodatkowe informacje:Jest to możliwe, ponieważ dzieje się to za kulisami:
Wygląda to na bardzo dziwną czynność, w ogóle niezalecaną. Podaj tylko inny sposób. Lepszym sposobem jest uczynienie struktury niezmienną i zapewnienie odpowiedniego konstruktora.
źródło
Origin.Y
? Biorąc pod uwagę właściwość typuPoint
, pomyślałbym, że idiomatyczny sposób zmianyX
byłby po prostu takivar temp=thing.Origin; temp.X = 23; thing.Origin = temp;
. Podejście idiomatyczne ma tę zaletę, że nie musi wspominać o członkach, których nie chce modyfikować, co jest możliwe tylko dlatego, żePoint
jest zmienne. Jestem zdziwiony filozofią, która mówi, że ponieważ kompilator nie może pozwolić,Origin.X = 23;
aby projekt struktury wymagał takiego koduOrigin.X = new Point(23, Origin.Y);
. To ostatnie wydaje mi się naprawdę obrzydliwe.X
iY
do określonego konstruktora). Teraz traci sens, kiedy można to zrobićPoint p = new Point()
. Wiem, dlaczego jest to naprawdę wymagane dla struktury, więc nie ma sensu o tym myśleć. Ale czy masz fajny pomysł na aktualizację tylko jednej właściwościX
?Object
. Oni tego nie robią. Każda definicja typu wartości w rzeczywistości definiuje dwa rodzaje rzeczy: typ miejsca przechowywania (używany dla zmiennych, pól tablicowych itp.) Oraz typ obiektu sterty, czasami nazywany typem „pudełkowym” (używany, gdy wartość typu wartości jest przechowywany w lokalizacji typu referencyjnego).Oprócz omawiania zalet i wad struktur kontra klas, staram się patrzeć na cel i podchodzić do problemu z tej perspektywy.
Biorąc to pod uwagę, jeśli nie musisz pisać kodu za właściwością metody get i set (jak w twoim przykładzie), to czy nie byłoby łatwiej po prostu zadeklarować the
Origin
jako pole klasy, a nie właściwość? Myślę, że to pozwoliłoby ci osiągnąć twój cel.źródło
Problem polega na tym, że wskazujesz wartość znajdującą się na stosie, a wartość nie zostanie ponownie przeniesiona z powrotem do właściwości oryginalnej, więc C # nie zezwala na zwrócenie odwołania do typu wartości. Myślę, że możesz rozwiązać ten problem, usuwając właściwość Origin i zamiast tego użyj publicznego pola, tak, wiem, że to nie jest miłe rozwiązanie. Innym rozwiązaniem jest nie używanie Point, a zamiast tego utworzenie własnego typu Point jako obiektu.
źródło
Point
jest członkiem typu referencyjnego, to nie będzie na stosie, będzie na stercie w pamięci obiektu zawierającego.Wydaje mi się, że haczyk polega na tym, że próbujesz przypisać wartości podrzędne obiektu w instrukcji, zamiast przypisywać sam obiekt. W tym przypadku musisz przypisać cały obiekt Point, ponieważ typ właściwości to Point.
Mam nadzieję, że mam tam sens
źródło
Point
był zmiennym typem klasy, oryginalny kod ustawiłby pole lub właściwośćX
w obiekcie zwróconym przez właściwośćOrigin
. Nie widzę powodu, by sądzić, że miałoby to pożądany wpływ na przedmiot zawierający tęOrigin
właściwość. Niektóre klasy Framework mają właściwości, które kopiują swój stan do nowych mutowalnych wystąpień klas i zwracają je. Taki projekt ma tę zaletę, że pozwala kodowithing1.Origin = thing2.Origin;
na ustawienie stanu pochodzenia obiektu tak, aby pasował do innego, ale nie może ostrzegać o kodzie takim jakthing1.Origin.X += 4;
.Po prostu usuń właściwość „get set” w następujący sposób, a wszystko będzie działać jak zawsze.
W przypadku typów pierwotnych instread użyj polecenia get; set; ...
źródło
Myślę, że wiele osób jest tu zdezorientowanych, ten konkretny problem jest związany ze zrozumieniem, że właściwości typu wartości zwracają kopię typu wartości (tak jak w przypadku metod i indeksatorów), a pola typu wartości są dostępne bezpośrednio . Poniższy kod robi dokładnie to, co próbujesz osiągnąć, uzyskując bezpośredni dostęp do pola zapasowego właściwości (uwaga: wyrażenie właściwości w postaci pełnej za pomocą pola zapasowego jest odpowiednikiem właściwości automatycznej, ale ma tę zaletę, że w naszym kodzie możemy uzyskać bezpośredni dostęp do pola zapasowego):
Otrzymany błąd jest pośrednią konsekwencją niezrozumienia, że właściwość zwraca kopię typu wartości. Jeśli otrzymasz kopię typu wartości i nie przypiszesz jej do zmiennej lokalnej, wszelkie zmiany, które wprowadzisz w tej kopii, nigdy nie mogą zostać odczytane, a zatem kompilator zgłasza to jako błąd, ponieważ nie może to być zamierzone. Jeśli przypiszemy kopię do zmiennej lokalnej, możemy zmienić wartość X, ale zostanie ona zmieniona tylko na kopii lokalnej, co naprawia błąd czasu kompilacji, ale nie będzie miało pożądanego efektu modyfikacji właściwości Origin. Poniższy kod ilustruje to, ponieważ błąd kompilacji zniknął, ale asercja debugowania zakończy się niepowodzeniem:
źródło