Boxvorkommen in C #


85

Ich versuche, alle Situationen zu erfassen, in denen Boxen in C # auftritt:

  • Werttyp in Typ System.Objectkonvertieren:

    struct S { }
    object box = new S();
    
  • Werttyp in Typ System.ValueTypekonvertieren:

    struct S { }
    System.ValueType box = new S();
    
  • Konvertieren des Werts des Aufzählungstyps in einen System.EnumTyp:

    enum E { A }
    System.Enum box = E.A;
    
  • Werttyp in Schnittstellenreferenz konvertieren:

    interface I { }
    struct S : I { }
    I box = new S();
    
  • Verwenden von Werttypen bei der Verkettung von C # -Strings:

    char c = F();
    string s1 = "char value will box" + c;
    

    Hinweis: Konstanten vom charTyp werden beim Kompilieren verkettet

    Anmerkung: seit Version 6.0 C # Compiler optimiert Verkettung beteiligt bool, char, IntPtr, UIntPtrArten

  • Erstellen eines Delegaten aus einer Instanzmethode mit Werttyp:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • Aufrufen nicht überschriebener virtueller Methoden für Werttypen:

    enum E { A }
    E.A.GetHashCode();
    
  • Verwenden von C # 7.0-Konstantenmustern unter isAusdruck:

    int x = …;
    if (x is 42) {  } // boxes both 'x' and '42'!
    
  • Boxen in C # -Tupeltypen Konvertierungen:

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • Optionale Parameter vom objectTyp mit Standardwerten für den Werttyp:

    void M([Optional, DefaultParameterValue(42)] object o);
    M(); // boxing at call-site
    
  • Überprüfen des Werts des nicht eingeschränkten generischen Typs auf null:

    bool M<T>(T t) => t != null;
    string M<T>(T t) => t?.ToString(); // ?. checks for null
    M(42);
    

    Hinweis: Dies kann in einigen .NET-Laufzeiten durch JIT optimiert werden

  • Typprüfwert vom uneingeschränkten oder structgenerischen Typ mit is/ asOperatoren:

    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);
    

    Hinweis: Dies kann in einigen .NET-Laufzeiten durch JIT optimiert werden

Gibt es weitere Situationen des Boxens, vielleicht versteckt, von denen Sie wissen?


2
Ich habe mich vor einiger Zeit damit befasst und fand das ziemlich interessant: Erkennen (un) boxen mit FxCop
George Duckett

Sehr schöne Conversion-Möglichkeiten, die Sie gezeigt haben. Tatsächlich kannte ich den 2. nicht oder habe ihn vielleicht nie ausprobiert :) Danke
Zenwalker

12
Es sollte eine Community-Wiki-Frage sein
Sly

2
Was ist mit nullbaren Typen? private int? nullableInteger
Allansson

1
@allansson, nullbare Typen sind nur eine Art von Werttypen
Kontrollfluss

Antworten:


42

Das ist eine gute Frage!

Boxen tritt aus genau einem Grund auf: wenn wir einen Verweis auf einen Werttyp benötigen . Alles, was Sie aufgelistet haben, fällt unter diese Regel.

Da Objekt beispielsweise ein Referenztyp ist, erfordert das Umwandeln eines Werttyps in ein Objekt einen Verweis auf einen Werttyp, was zu Boxen führt.

Wenn Sie alle möglichen Szenarien auflisten möchten, sollten Sie auch Ableitungen einschließen, z. B. die Rückgabe eines Werttyps von einer Methode, die ein Objekt oder einen Schnittstellentyp zurückgibt, da dadurch der Werttyp automatisch in das Objekt / die Schnittstelle umgewandelt wird.

Übrigens ergibt sich der von Ihnen scharfsinnig identifizierte Fall der Verkettung von Zeichenfolgen auch aus der Umwandlung in ein Objekt. Der Operator + wird vom Compiler in einen Aufruf der Concat-Zeichenfolgenmethode übersetzt, die ein Objekt für den von Ihnen übergebenen Werttyp akzeptiert, sodass eine Umwandlung in ein Objekt und damit ein Boxen erfolgt.

Im Laufe der Jahre habe ich Entwicklern immer geraten, sich an den einzigen Grund für das Boxen zu erinnern (den ich oben angegeben habe), anstatt sich jeden einzelnen Fall zu merken, da die Liste lang und schwer zu merken ist. Dies fördert auch das Verständnis dessen, welchen IL-Code der Compiler für unseren C # -Code generiert (z. B. + on string führt zu einem Aufruf von String.Concat). Wenn Sie Zweifel haben, was der Compiler generiert, und wenn Boxen auftritt, können Sie IL Disassembler (ILDASM.exe) verwenden. Normalerweise sollten Sie nach dem Box-Opcode suchen (es gibt nur einen Fall, in dem Boxen auftreten kann, obwohl die IL den Box-Opcode nicht enthält, weitere Details siehe unten).

Aber ich stimme zu, dass einige Boxereignisse weniger offensichtlich sind. Sie haben eine davon aufgelistet: Aufrufen einer nicht überschriebenen Methode eines Werttyps. In der Tat ist dies aus einem anderen Grund weniger offensichtlich: Wenn Sie den IL-Code überprüfen, sehen Sie nicht den Box-Opcode, sondern den Constraint-Opcode. Selbst in der IL ist es nicht offensichtlich, dass Boxen stattfindet! Ich werde nicht ins Detail gehen, warum ich verhindern soll, dass diese Antwort noch länger wird ...

Ein weiterer Fall für weniger offensichtliches Boxen ist das Aufrufen einer Basisklassenmethode aus einer Struktur. Beispiel:

struct MyValType
{
    public override string ToString()
    {
        return base.ToString();
    }
}

Hier wird ToString überschrieben, sodass das Aufrufen von ToString auf MyValType kein Boxen erzeugt. Die Implementierung ruft jedoch den Basis-ToString auf und dies führt zu Boxen (überprüfen Sie die IL!).

Übrigens leiten sich diese beiden nicht offensichtlichen Boxszenarien auch aus der obigen Einzelregel ab. Wenn eine Methode für die Basisklasse eines Werttyps aufgerufen wird, muss das Schlüsselwort this auf etwas verweisen. Da die Basisklasse eines Werttyps (immer) ein Referenztyp ist, muss dieses Schlüsselwort auf einen Referenztyp verweisen. Daher benötigen wir einen Verweis auf einen Werttyp, sodass das Boxen aufgrund der einzelnen Regel auftritt.

Hier ist ein direkter Link zu dem Abschnitt meines Online-.NET-Kurses, in dem das Boxen ausführlich behandelt wird: http://motti.me/mq

Wenn Sie nur an fortgeschritteneren Boxszenarien interessiert sind, finden Sie hier einen direkten Link (obwohl der obige Link Sie auch dorthin führt, sobald er die grundlegenderen Dinge bespricht): http://motti.me/mu

Ich hoffe das hilft!

Motti


1
Wenn a ToString()für einen bestimmten Werttyp aufgerufen wird, der ihn nicht überschreibt, wird der Werttyp am Aufrufstandort eingerahmt oder die Methode (ohne Boxing) an eine automatisch generierte Überschreibung gesendet, die nur eine Kette (mit) ausführt Boxen) zur Basismethode?
Supercat

@supercat Das Aufrufen einer Methode, die baseeinen Werttyp aufruft, führt zum Boxen. Dies umfasst virtuelle Methoden, die nicht von der Struktur überschrieben werden, und ObjectMethoden, die überhaupt nicht virtuell sind (wie GetType()). Siehe diese Frage .
Şafak Gür

@ ŞafakGür: Ich weiß, dass das Endergebnis Boxen sein wird. Ich habe mich über den genauen Mechanismus gewundert, über den es passiert. Da der Compiler, der IL generiert, möglicherweise nicht weiß, ob der Typ ein Werttyp oder eine Referenz ist (er kann generisch sein), wird unabhängig davon ein Callvirt generiert. Der JITter würde wissen, ob der Typ ein Werttyp ist und ob er ToString überschreibt, sodass er Call-Site-Code für das Boxen generieren kann. alternativ könnte es automatisch für jede Struktur generiert werden, die ToStringein Mehtod nicht überschreibt public override void ToString() { return base.ToString(); }und ...
Supercat

... das Boxen innerhalb dieser Methode auftreten lassen. Da die Methode sehr kurz wäre, könnte sie dann inline sein. Wenn Sie mit dem letzteren Ansatz arbeiten, kann auf die ToString()Methode einer Struktur wie auf jede andere über Reflection zugegriffen und ein statischer Delegat erstellt werden, der den Strukturtyp als refParameter verwendet [so etwas funktioniert mit nicht vererbten Strukturmethoden], aber ich Ich habe gerade versucht, einen solchen Delegaten zu erstellen, und es hat nicht funktioniert. Ist es möglich, einen statischen Delegaten für die ToString()Methode einer Struktur zu erstellen , und wenn ja, wie sollte der Parametertyp lauten?
Supercat

Links sind kaputt.
OfirD

5

Aufrufen der nicht virtuellen GetType () -Methode für den Werttyp:

struct S { };
S s = new S();
s.GetType();

2
GetTypeerfordert Boxen nicht nur, weil es nicht virtuell ist, sondern weil Wertspeicherorte im Gegensatz zu Heap-Objekten kein "verstecktes" Feld haben, GetType()mit dem sie ihren Typ identifizieren können.
Supercat

@ Supercat Hmmm. 1. Vom Compiler hinzugefügtes Boxing und verstecktes Feld, das von der Laufzeit verwendet wird. Möglicherweise fügt der Compiler Boxing hinzu, weil er über die Laufzeit Bescheid weiß… 2. Wenn wir nicht-virtuellen ToString (Zeichenfolge) für den Enum-Wert aufrufen, ist auch Boxing erforderlich, und ich glaube nicht, dass der Compiler dies hinzufügt, weil er die Implementierungsdetails von Enum.ToString (Zeichenfolge) kennt . Daher kann ich, glaube ich, sagen, dass das Boxen immer dann auftrat, wenn eine nicht virtuelle Methode für den "Basiswerttyp" aufgerufen wurde.
Viacheslav Ivanov

Ich hatte nicht in Betracht gezogen Enum, eigene nicht virtuelle Methoden zu haben , obwohl eine ToString()Methode für ein EnumZugriff auf Typinformationen benötigen würde. Ich frage mich, ob Object, ValueTypeoder Enumhat nicht virtuelle Methoden, die ihre Jobs ohne Typinformationen ausführen könnten.
Supercat

3

Erwähnt in Mottis Antwort, nur zur Veranschaulichung mit Codebeispielen:

Beteiligte Parameter

public void Bla(object obj)
{

}

Bla(valueType)

public void Bla(IBla i) //where IBla is interface
{

}

Bla(valueType)

Aber das ist sicher:

public void Bla<T>(T obj) where T : IBla
{

}

Bla(valueType)

Rückgabetyp

public object Bla()
{
    return 1;
}

public IBla Bla() //IBla is an interface that 1 inherits
{
    return 1;
}

Unbeschränktes T gegen Null prüfen

public void Bla<T>(T obj)
{
    if (obj == null) //boxes.
}

Verwendung von Dynamik

dynamic x = 42; (boxes)

Noch einer

enumValue.HasFlag


0
  • Verwenden der nicht generischen Sammlungen in System.Collectionswie ArrayListoder HashTable.

Zugegeben, dies sind bestimmte Fälle Ihres ersten Falls, aber es können versteckte Fallstricke sein. Es ist erstaunlich, wie viel Code mir heute noch begegnet, der diese anstelle von List<T>und verwendet Dictionary<TKey,TValue>.


0

Das Hinzufügen eines Werttyps zur ArrayList führt zum Boxen:

ArrayList items = ...
numbers.Add(1); // boxing to object
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.