Próbuję zebrać wszystkie sytuacje, w których boks występuje w C #:
Konwersja typu wartości na
System.Object
typ:struct S { } object box = new S();
Konwersja typu wartości na
System.ValueType
typ:struct S { } System.ValueType box = new S();
Konwersja wartości typu wyliczenia na
System.Enum
typ:enum E { A } System.Enum box = E.A;
Konwersja typu wartości na odniesienie do interfejsu:
interface I { } struct S : I { } I box = new S();
Używanie typów wartości w konkatenacji ciągów C #: Using value types in C # string concatenation:
char c = F(); string s1 = "char value will box" + c;
uwaga: stałe
char
typu są łączone w czasie kompilacjiUwaga: od wersji 6.0 C # kompilator optymalizuje konkatenacji udziałem
bool
,char
,IntPtr
,UIntPtr
rodzajeTworzenie delegata z metody wystąpienia typu wartości:
struct S { public void M() {} } Action box = new S().M;
Wywołanie niezastępowanych metod wirtualnych na typach wartości:
enum E { A } E.A.GetHashCode();
Korzystanie ze stałych wzorców języka C # 7.0 w
is
wyrażeniem:int x = …; if (x is 42) { … } // boxes both 'x' and '42'!
Boks w C # konwersjach typów krotek:
(int, byte) _tuple; public (object, object) M() { return _tuple; // 2x boxing }
Opcjonalne parametry
object
typu z wartościami domyślnymi typu wartości:void M([Optional, DefaultParameterValue(42)] object o); M(); // boxing at call-site
Sprawdzanie wartości nieograniczonego typu ogólnego dla
null
:bool M<T>(T t) => t != null; string M<T>(T t) => t?.ToString(); // ?. checks for null M(42);
Uwaga: może to zostać zoptymalizowane przez JIT w niektórych środowiskach .NET
Wartość testowania typu typu nieograniczonego lub
struct
ogólnego z operatoramiis
/as
:bool M<T>(T t) => t is int; int? M<T>(T t) => t as int?; IEquatable<T> M<T>(T t) => t as IEquatable<T>; M(42);
uwaga: może to zostać zoptymalizowane przez JIT w niektórych środowiskach .NET
Czy są jakieś sytuacje bokserskie, może ukryte, o których wiesz?
źródło
private int? nullableInteger
Odpowiedzi:
To świetne pytanie!
Boks pojawia się dokładnie z jednego powodu: kiedy potrzebujemy odniesienia do typu wartości . Wszystko, co podałeś, podlega tej zasadzie.
Na przykład, ponieważ obiekt jest typem referencyjnym, rzutowanie typu wartości na obiekt wymaga odwołania do typu wartości, co powoduje zapakowanie.
Jeśli chcesz wymienić wszystkie możliwe scenariusze, powinieneś również uwzględnić pochodne, takie jak zwracanie typu wartości z metody, która zwraca obiekt lub typ interfejsu, ponieważ powoduje to automatyczne rzutowanie typu wartości na obiekt / interfejs.
Nawiasem mówiąc, przypadek konkatenacji ciągów, który trafnie zidentyfikowałeś, również pochodzi z rzutowania na obiekt. Operator + jest tłumaczony przez kompilator na wywołanie metody ciągowej Concat, która akceptuje obiekt dla przekazanego typu wartości, więc następuje rzutowanie na obiekt i tym samym tworzenie pudełek.
Przez lata zawsze doradzałem programistom, aby pamiętali jeden powód boksu (wskazałem powyżej) zamiast zapamiętywać każdy przypadek, ponieważ lista jest długa i trudna do zapamiętania. Ułatwia to również zrozumienie, jaki kod IL generuje kompilator dla naszego kodu C # (na przykład + on string daje wywołanie String.Concat). Jeśli masz wątpliwości, co generuje kompilator i jeśli wystąpi boxing, możesz użyć IL Disassembler (ILDASM.exe). Zwykle powinieneś szukać kodu operacyjnego pudełka (jest tylko jeden przypadek, w którym może wystąpić boks, nawet jeśli IL nie zawiera kodu operacyjnego pudełka, więcej szczegółów poniżej).
Ale zgadzam się, że niektóre wydarzenia bokserskie są mniej oczywiste. Wymieniłeś jeden z nich: wywołanie niezastępowanej metody typu wartości. W rzeczywistości jest to mniej oczywiste z innego powodu: kiedy sprawdzasz kod IL, nie widzisz kodu operacyjnego pudełka, ale kod ograniczenia, więc nawet w IL nie jest oczywiste, że ma miejsce boks! Nie będę wchodził w szczegóły, dlaczego nie mam dopuścić do tego, aby ta odpowiedź stała się jeszcze dłuższa ...
Innym przypadkiem mniej oczywistego boksu jest wywołanie metody klasy bazowej ze struktury. Przykład:
struct MyValType { public override string ToString() { return base.ToString(); } }
Tutaj ToString jest zastępowane, więc wywołanie ToString w MyValType nie spowoduje wygenerowania boksu. Jednak implementacja wywołuje podstawowy ToString, co powoduje boksowanie (sprawdź IL!).
Nawiasem mówiąc, te dwa nieoczywiste scenariusze bokserskie również wywodzą się z jednej zasady powyżej. Gdy metoda jest wywoływana w klasie bazowej typu wartości, musi istnieć coś, do czego słowo kluczowe this może się odwoływać. Ponieważ klasa bazowa typu wartości jest (zawsze) typem referencyjnym, metoda this słowo kluczowe musi odnosić się do typu referencyjnego, dlatego potrzebujemy odwołania do typu wartości, a więc boksowanie występuje z powodu pojedynczej reguły.
Oto bezpośredni link do sekcji mojego kursu online .NET, który szczegółowo omawia boks: http://motti.me/mq
Jeśli interesują Cię tylko bardziej zaawansowane scenariusze bokserskie, tutaj znajduje się bezpośredni link (chociaż powyższy link również Cię tam przeniesie, gdy omówi bardziej podstawowe rzeczy): http://motti.me/mu
Mam nadzieję, że to pomoże!
Motti
źródło
ToString()
jest wywoływana dla określonego typu wartości, który go nie przesłania, czy typ wartości zostanie umieszczony w miejscu wywołania, czy też metoda zostanie wysłana (bez ramki) do automatycznie wygenerowanego zastąpienia, które nie robi nic poza łańcuchem (z boks) do metody podstawowej?base
typ wartości, spowoduje boksowanie. Obejmuje to metody wirtualne, które nie są zastępowane przez strukturę, iObject
metody, które w ogóle nie są wirtualne (jakGetType()
). Zobacz to pytanie .ToString
mehtodapublic override void ToString() { return base.ToString(); }
i ...ToString()
dostęp do metody struktury przez Reflection, tak jak każda inna i służy do tworzenia statycznego delegata, który przyjmuje typ struktury jakoref
parametr [taka rzecz działa z niedziedziczonymi metodami struktury], ale ja po prostu próbowałem stworzyć takiego delegata i nie udało się. Czy można utworzyć delegata statycznego dlaToString()
metody struktury , a jeśli tak, to jaki powinien być typ parametru?Wywołanie niewirtualnej metody GetType () na typie wartości:
struct S { }; S s = new S(); s.GetType();
źródło
GetType
wymaga boksu nie tylko dlatego, że nie jest wirtualny, ale dlatego, że lokalizacje przechowywania typu wartości, w przeciwieństwie do obiektów sterty, nie mają „ukrytego” pola, któregoGetType()
można użyć do identyfikacji ich typu.Enum
posiadania własnych metod niewirtualnych, chociażToString()
metoda dla osobyEnum
wymagałaby dostępu do informacji o typie. Zastanawiam się, czyObject
,ValueType
czyEnum
ma jakieś metody nie wirtualne, które mogą wykonywać swoje zadania bez informacji o typie.Wspomniane w odpowiedzi Mottiego, ilustrujące tylko przykładami kodu:
Uwzględnione parametry
public void Bla(object obj) { } Bla(valueType) public void Bla(IBla i) //where IBla is interface { } Bla(valueType)
Ale to jest bezpieczne:
public void Bla<T>(T obj) where T : IBla { } Bla(valueType)
Typ zwrotu
public object Bla() { return 1; } public IBla Bla() //IBla is an interface that 1 inherits { return 1; }
Sprawdzanie nieograniczonego T względem zera
public void Bla<T>(T obj) { if (obj == null) //boxes. }
Korzystanie z dynamiki
dynamic x = 42; (boxes)
Inny
enumValue.HasFlag
źródło
System.Collections
takich jakArrayList
lubHashTable
.To prawda, że są to określone przypadki twojego pierwszego przypadku, ale mogą to być ukryte problemy. To zdumiewające ilość kodu, z którym wciąż się dziś spotykam, w którym są używane zamiast
List<T>
iDictionary<TKey,TValue>
.źródło
Dodanie dowolnej wartości typu wartości do tablicy ArrayList powoduje opakowanie:
ArrayList items = ... numbers.Add(1); // boxing to object
źródło