Powracamy do podstaw, tak – dla sportu.
Przekazywanie zmiennej typu wartościowego, takiej jak int
czy czy struct
do metody przez referencję oraz zwracanie wartości z metody przez referencję to coś, co rzadko wykorzystuje się na co dzień, dlatego od czasu do czasu warto odświeżyć sobie tę wiedzę.
W załączonym kodzie możesz prześledzić różnice między przekazywaniem argumentów przez wartość oraz przez referencję. Przedstawiam przykłady przekazywania typów wartościowych oraz referencyjnych jako argumenty do metod i modyfikowania ich wartości wewnątrz metod.
Przed przejściem do kodu, należy pamiętać, że przekazywanie zmiennych przez referencję może prowadzić do trudniejszego zrozumienia kodu i jego debugowania. Dlatego należy zachować ostrożność i rozważyć odpowiednio użycie tej techniki.
Program wyświetli wartości zmiennych przed i po przekazaniu ich jako argument do metod. Ilustruje różnice między przekazywaniem przez wartość i przez referencję, a także wpływ modyfikowania wartości argumentów wewnątrz metod.
// Przejdź do GitHub, aby przeanalizować całość kodu
// wraz z wydrukowanymi wynikami i komentarzami
var globalInt = 100;
ArgIntMethod(globalInt);
ArgRefIntMethod(ref globalInt);
var globalStruct = new FirstStruct { /***/};
ArgStructMethod(globalStruct);
ArgRefStructMethod(ref globalStruct);
var globalClass = new Class { /***/};
EditPropsOfArgClassMethod(globalClass);
EditPropsOfArgRefClassMethod(ref globalClass);
Przedstawiłem również przykłady zwracania z metody typów wartościowych oraz referencyjnych przez referencję oraz ich przypisanie do lokalnych referencyjnych typów wartościowych.
GitHub: Ref Locals, Ref Returns
// Przejdź do GitHub, aby przeanalizować całość kodu
// wraz z wydrukowanymi wynikami i komentarzami
var globalClassObject = new Class();
var value = globalClassObject.GetInt();
value += 100;
ref var localRef = ref globalClassObject.RefGetInt();
localRef += 100;
var localValueType = globalClassObject.RefGetInt();
localValueType += 1000;
var complexObject = new Object();
var dto = complexObject.GetDto();
dto.IntProp += 111;
dto = new Dto { IntProp = 1_000 };
ref var refDto = ref complexObject.RefGetDto();
refDto = new Dto { IntProp = 5_000_000 };
internal class Class
{
private int _valueTypeVariable = 100;
public int GetInt() => _valueTypeVariable;
public ref int RefGetInt() => ref _valueTypeVariable;
}
internal class Object
{
private Dto _dto = new ();
public Dto GetDto() =>_dto;
public ref Dto RefGetDto() => ref _dto;
}
internal class Dto
{
public int IntProp { get; set; } = 111;
}
Przekazując do metody zmienną typu wartościowego, jej wartość nie jest trwale zmodyfikowana poza tą metodą. Natomiast, gdy przekazujemy je przez referencję, jej wartość może zostać zmodyfikowana wewnątrz metody, co będzie miało wpływ na jej wartość poza metodą.
Podsumowując, gdy potrzebujemy zoptymalizować wydajność swojej aplikacji, czasami lepiej jest używać typów wartościowych, zamiast typów referencyjnych. Powodem jest to, że typy wartościowe są przechowywane na stosie, a nie na starcie, co oznacza, że GC ma mniej pracy, ponieważ nie musi śledzić i czyścić nieużywanych obiektów.
Kolejnym sposobem na poprawienie wydajności programu jest przekazywanie typów wartościowych przez referencję. Unikamy w ten sposób nadmiaru kopiowania wartości do zasięgu lokalnego metody, zwłaszcza gdy pracujemy z dużymi wartościowymi lub często wywołujemy metodę.
Pamiętajcie, że przekazywanie argumentów przez referencję może być użyteczne w celu poprawienia wydajności, ale należy zachować ostrożność i rozważyć, czy jest to odpowiednie dla danego przypadku.
P.S. Większą część tekstu na blogu i w GitHub oraz komentarze w kodzie wygenerował za mnie ChatGPT (nie wiem, czy chciałoby mi się to wszystko pisać samemu xD).