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.
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.
int value = 15; object box = value;
- Konwersja typu wartościowego na typ System.ValueType
decimal value = 200.0m; ValueType box = value;
- Konwersja typu wyliczeniowego na typ System.Enum.
Enum box = Role.Student;
- Konwersja typu wartościowego na typ dynamic.
short value = 14; dynamic box = value;
- Konwersja typu wartościowego na typ interfejsu.
IVector 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.
public object GetObject() => 13.34f; public ValueType GetValue() => 23; public Enum GetRole() => Role.Student; public dynamic GetAge() => 18; public IVector GetVector() => new Vector();
public void SetObject(object obj) { }
public void SetValue(ValueType value) { }
public void SetRole(Enum role) { }
public void SetAge(dynamic age) { }
public void SetVector(IVector vector) { }
SetObject(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).
public void SetVector(T vector) where T: IVector { }
- Dodawanie elementu do kolekcji typu List.
var list = new List
- Dodawanie elementu do nie generycznych kolekcji np. ArrayList, HashTable, SortedList etc.
var arrayList = new ArrayList { "65" };
var hashTable = new Hashtable { { "key", "value" } };
var sortedList = new SortedList { { "key", "value" } };
- Sprawdzenie czy argument metody nie jest null.
public bool IsNull(T obj) => obj == null; public string GetMessage (T obj) => obj?.ToString();
- Wywołanie nieprzesłoniętych wirtualnych metod na typach wartościowych.
var 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
- Wywołanie metody klasy bazowej w strukturze.
public 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.
var sample = new SampleStruct(); var type = sample.GetType();
- Powiązanie zadeklarowanego delegata z metodą typu wartościowego.
var sample = new SampleStruct(); Actionbox = sample.SampleMethod;
- Konkatenacja stringa z użyciem typu wartościowego
char 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.
string 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.



