Co oznaczają pojęcia boxing i unboxing? Jeśli ktoś kiedyś przygotowywał się do rozmowy kwalifikacyjnej czy do zaliczenia z podstaw języka C# na studiach, na pewno słyszał/czytał, że powyższe pytanie to pewniak na 100% o to spytają. Może i tak było, ale czy oprócz wykutych regułek orientujecie się, kiedy boxing występuję w praktyce na placu boju. W artykule omówię znane mi przykłady boxingu.
Teoria
Na początek zanim przejdę do praktyki w ramach wyrównanie wiedzy omówię dwa podstawowe terminy powiązane z bieżącym wpisem.
Boxing – jawna/niejawna konwersja typu wartościowego na typ referencyjny
Unboxing – jawna konwersja typu referencyjnego na typ wartościowy
Pluginy do VS i R#
Visual Studio jest zazwyczaj codziennym środowiskiem pracy programisty C#. Wykorzystajmy to i wspomóżmy sobie wykrywanie ukrytych alokacji poprzez instalacje jednej z wtyczek:
Wtyczki analizują kod w C# w celu znalezienia ukrytych alokacji. Miejsca występowania boxingu prezentowane są podkreśleniem, po najechaniu na zmienną otrzymamy szczegółowy komunikat o wykrytym problemie.
Praktyka (przypadki występowania)
W analizowanych przykładach będę bazował na poniższych strukturach danych.
1 2 3 4 5 6 7 8 9 10 |
public enum Role { Student } public interface IVector { } public struct Vector : IVector { } public struct SampleStruct { public void SampleMethod(string msg) => Console.WriteLine(msg); } |
Boxing występuje w następujących przypadkach:
- Konwersja typu wartościowego na typ System.Object.
12int value = 15;object box = value; - Konwersja typu wartościowego na typ System.ValueType.
12decimal value = 200.0m;ValueType box = value; - Konwersja typu wyliczeniowego na typ System.Enum.
1Enum box = Role.Student; - Konwersja typu wartościowego na typ dynamic.
12short value = 14;dynamic box = value; - Konwersja typu wartościowego na typ interfejsu.
1IVector box = new Vector(); - Omawiane powyżej przykłady boxingu, występują także w momencie zwracania wartości wyniku z metody, oraz w sytuacji przekazywania argumentu do metody.
12345public object GetObject() => 13.34f;public ValueType GetValue() => 23;public Enum GetRole() => Role.Student;public dynamic GetAge() => 18;public IVector GetVector() => new Vector();
12345public void SetObject(object obj) { }public void SetValue(ValueType value) { }public void SetRole(Enum role) { }public void SetAge(dynamic age) { }public void SetVector(IVector vector) { }
12345SetObject(13.34f);SetValue(23);SetRole(Role.Student);SetAge(21);SetVector(new Vector());
W sytuacji przekazywania implementacji interfejsu IVector, możemy lekko zmodyfikować metodę w celu uniknięcia boxingu (kod poniżej).
1public void SetVector<T>(T vector) where T: IVector { } - Dodawanie elementu do kolekcji typu List<object>.
123var list = new List<object>();list.Add(23);list.Add('d'); - Dodawanie elementu do nie generycznych kolekcji np. ArrayList, HashTable, SortedList etc.
123var arrayList = new ArrayList { "65" };var hashTable = new Hashtable { { "key", "value" } };var sortedList = new SortedList { { "key", "value" } }; - Sprawdzenie czy argument metody nie jest null.
12public bool IsNull<T>(T obj) => obj == null;public string GetMessage<T>(T obj) => obj?.ToString(); - Wywołanie nieprzesłoniętych wirtualnych metod na typach wartościowych.
123var sample = new SampleStruct();var hash = sample.GetHashCode();var s = sample.ToString();
W powyższym przypadku w celu uniknięcia boxingu wymagana jest implementacja interfejsu IEquatable<T> i przesłonięcie metody ToString(). - Wywołanie metody klasy bazowej w strukturze.
1234567public struct SampleStruct{public override string ToString(){return base.ToString();}}
W powyższym kodzie został rozwiązany wcześniejszy problem dla wywołanie nieprzesłoniętej metody wirtualnej. Jednak w ciele metody ToString() wywołujemy metodę z klasy bazowej, co powoduje w tym przypadku boxing. - Wywołanie metody GetType() na typie wartościowym.
12var sample = new SampleStruct();var type = sample.GetType(); - Powiązanie zadeklarowanego delegata z metodą typu wartościowego.
12var sample = new SampleStruct();Action<string> box = sample.SampleMethod; - Konkatenacja stringa z użyciem typu wartościowego.
12345char nextChar = 'z';string s = "xy" + nextChar;string sFormat = String.Format("xy{0}", nextChar);string sInter = $"xy{nextChar}";string sConcat = string.Concat("xy", nextChar);
W celu zrozumienia wystąpienia boxingu przy konkatenacji stringa podejrzyjmy sobie definicje operatora +. Jako drugi parametr przyjmowany jest wartość typu object.
1string string.operator +(string left, object right)
Podejrzyjmy IL
Jeśli nie lubimy korzystać z pluginów, które podkreślają nam miejsca występowania ukrytej alokacji w Visual Studio możemy zawsze przeanalizować kod IL. Tutaj zaprezentuję dodatkowy przypadek boxingu, którego nie wymieniłem wcześniej w rozdziale Praktyka. W celu kompilacji instrukcji C# do kodu pośredniego IL wykorzystam program LINQPad.
W sytuacji użycia dopasowania wzorca (pattern matching) z stałą w instrukcji z is dochodzi do boxingu. Nie będę omawiał po kolei każdej instrukcji IL to, co w tej chwili nas interesuje to wystąpienie instrukcji box (Convert a boxable value to its boxed form) oznaczającej wystąpienie boxingu.
Podsumowanie
Dzisiejszy wpis sięga do postaw, o których klepiąc kolejne klasy typu CRUD zapominamy. Mam nadzieje, że udało mi się w prosty sposób zaprezentować na przykładach wystąpienie boxingu. Podsumowując boxing i unboxing są kosztownymi konwersjami głównie ze względu na alokacje dodatkowej pamięci podczas boxingu (GC będzie musiał po wszystkim posprzątać). Jeśli znasz przypadek wystąpienia boxingu, o którym nie wspomniałem to zapraszam do podzielenia się w komentarzu pod tym wpisem.