Frage

Meine Frage hängt etwas mit diesem zusammen: Wie verhindert eine generische Einschränkung das Boxen eines Werttyps mit einer implizit implementierten Schnittstelle?, aber anders, weil es keine Einschränkung brauchen sollte, um dies zu tun, weil es überhaupt nicht generisch ist.

Ich habe den Code

interface I { void F(); }
struct C : I { void I.F() {} }
static class P {
    static void Main()
    {    
        C x;
        ((I)x).F();
    }
}

Die Hauptmethode kompiliert dazu:

IL_0000:  ldloc.0
IL_0001:  box        C
IL_0006:  callvirt   instance void I::F()
IL_000b:  ret

Warum kompiliert es das nicht?

IL_0000:  ldloca.s   V_0
IL_0002:  call       instance void C::I.F()
IL_0007:  ret

Ich sehe, warum Sie eine Methode -Tabelle benötigen, um einen virtuellen Anruf zu tätigen, aber Sie müssen in diesem Fall keinen virtuellen Anruf tätigen. Wenn die Schnittstelle normalerweise implementiert ist, wird kein virtueller Anruf getätigt.

Auch verwandt: Warum sind explizite Schnittstellenimplementierungen privat? - Die vorhandenen Antworten auf diese Frage erklären nicht angemessen, warum die Methoden in den Metadaten als privat markiert sind (anstatt nur unbrauchbare Namen). Aber auch das erklärt nicht ganz, warum es gebeugt ist, da es immer noch Boxen von Innenstadt C.

War es hilfreich?

Lösung

Ich denke, die Antwort liegt in der C# -Anspezifikation, wie Schnittstellen behandelt werden können. Aus der Spezifikation:

Es gibt verschiedene Arten von Variablen in C#, einschließlich Feldern, Array -Elementen, lokalen Variablen und Parametern. Variablen stellen Speicherplätze dar, und jede Variable hat einen Typ, der bestimmt, welche Werte in der Variablen gespeichert werden können, wie in der folgenden Tabelle gezeigt.

Unter der folgenden Tabelle heißt es für eine Schnittstelle

Eine Nullreferenz, eine Referenz auf eine Instanz eines Klassentyps, der diesen Schnittstellentyp implementiert, oder eine Referenz auf einen Box -Wert eines Werttyps, der diesen Schnittstellentyp implementiert

Es heißt explizit, dass es sich um einen Box -Wert eines Werttyps handelt. Der Compiler befolgt nur der Spezifikation

** Bearbeiten **

Weitere Informationen auf der Grundlage des Kommentars hinzufügen. Der Compiler kann kostenlos umschreiben, wenn er den gleichen Effekt hat. Da das Boxing jedoch auftritt, erstellen Sie eine Kopie des Werttyps nicht denselben Werttyp. Aus der Spezifikation noch einmal:

Eine Konvertierung des Boxs impliziert, dass eine Kopie des Wertes des Wertes erstellt wird. Dies unterscheidet sich von einer Konvertierung eines Referenztyps zum Typ-Objekt, bei dem der Wert weiterhin auf dieselbe Instanz rezidiviert und einfach als weniger abgeleitetes Objekt angesehen wird.

Dies bedeutet, dass es jedes Mal das Boxen machen muss oder Sie inkonsistentes Verhalten erhalten. Ein einfaches Beispiel hierfür kann angezeigt werden, indem Sie Folgendes mit dem bereitgestellten Programm durchführen:

public interface I { void F(); }
public struct C : I {
    public int i;
    public void F() { i++; } 
    public int GetI() { return i; }
}

    class P
    {
    static void Main(string[] args)
    {
        C x = new C();
        I ix = (I)x;
        ix.F();
        ix.F();
        x.F();
        ((I)x).F();
        Console.WriteLine(x.GetI());
        Console.WriteLine(((C)ix).GetI());
        Console.ReadLine();
    }
}

Ich habe Struktur ein internes Mitglied hinzugefügt C das wird jedes Mal um 1 erhöht, wenn das ist F() wird dieses Objekt aufgerufen. Auf diese Weise können wir sehen, was mit den Daten unseres Werttyps passiert. Wenn das Boxen nicht durchgeführt wurde x Dann würden Sie erwarten, dass das Programm 4 für beide Anrufe aufschreibt GetI() wie wir anrufen F() vier Mal. Das tatsächliche Ergebnis ist jedoch 1 und 2. Der Grund dafür ist, dass das Boxing eine Kopie erstellt hat.

Dies zeigt uns, dass es einen Unterschied zwischen dem Wert des Wertes gibt und ob wir den Wert nicht boxen

Andere Tipps

Der Wert nicht Notwendig Holen Sie sich. Der C#-TO-MSIL-Übersetzungsschritt macht normalerweise die meisten coolen Optimierungen nicht (aus wenigen Gründen, zumindest einige davon sind wirklich gute), sodass Sie wahrscheinlich immer noch das sehen werden box Anweisung Wenn Sie sich den MSIL ansehen, aber die JIT die tatsächliche Zuordnung manchmal legal elide, wenn sie erkennt, dass sie damit durchkommen kann. Ab .NET FAT 4.7.1 sieht es so aus, als hätten die Entwickler nie in das Lehren des JIT investiert, wie sie herausfinden sollen, wann dies legal war. Die JIT von .net Core 2.1 macht dies (nicht sicher, wann es hinzugefügt wurde, ich weiß nur, dass es in 2.1 funktioniert).

Hier sind die Ergebnisse eines Benchmarks, den ich beweisen kann, um es zu beweisen:

BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
Frequency=3515626 Hz, Resolution=284.4444 ns, Timer=TSC
.NET Core SDK=2.1.302
  [Host] : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
  Clr    : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3131.0
  Core   : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT


                Method |  Job | Runtime |     Mean |     Error |    StdDev |  Gen 0 | Allocated |
---------------------- |----- |-------- |---------:|----------:|----------:|-------:|----------:|
       ViaExplicitCast |  Clr |     Clr | 5.139 us | 0.0116 us | 0.0109 us | 3.8071 |   24000 B |
 ViaConstrainedGeneric |  Clr |     Clr | 2.635 us | 0.0034 us | 0.0028 us |      - |       0 B |
       ViaExplicitCast | Core |    Core | 1.681 us | 0.0095 us | 0.0084 us |      - |       0 B |
 ViaConstrainedGeneric | Core |    Core | 2.635 us | 0.0034 us | 0.0027 us |      - |       0 B |

Benchmark -Quellcode:

using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using BenchmarkDotNet.Running;

[MemoryDiagnoser, ClrJob, CoreJob, MarkdownExporterAttribute.StackOverflow]
public class Program
{
    public static void Main() => BenchmarkRunner.Run<Program>();

    [Benchmark]
    public int ViaExplicitCast()
    {
        int sum = 0;
        for (int i = 0; i < 1000; i++)
        {
            sum += ((IValGetter)new ValGetter(i)).GetVal();
        }

        return sum;
    }

    [Benchmark]
    public int ViaConstrainedGeneric()
    {
        int sum = 0;
        for (int i = 0; i < 1000; i++)
        {
            sum += GetVal(new ValGetter(i));
        }

        return sum;
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static int GetVal<T>(T val) where T : IValGetter => val.GetVal();

    public interface IValGetter { int GetVal(); }

    public struct ValGetter : IValGetter
    {
        public int _val;

        public ValGetter(int val) => _val = val;

        [MethodImpl(MethodImplOptions.NoInlining)]
        int IValGetter.GetVal() => _val;
    }
}

Das Problem ist, dass es keinen Wert oder eine Variable gibt, die "nur" ein Schnittstellentyp ist. Wenn ein Versuch unternommen wird, eine solche Variable zu definieren oder zu einem solchen Wert zu werfen, ist der reale Typ, der verwendet wird, effektiv "und eine Object Das implementiert die Schnittstelle ".

Diese Unterscheidung kommt mit Generika ins Spiel. Angenommen, eine Routine akzeptiert einen Typparameter des Typs T wo T:IFoo. Wenn man eine solche Routine übergibt, die IFOO implementiert, ist der übergegebene Parameter kein Klassentyp, der vom Objekt erbt, sondern stattdessen der entsprechende Strukturtyp ist. Wenn die Routine den überschrittenen Parameter einer lokalen Variablen vom Typ zuweisen sollte T, der Parameter würde durch Wert ohne Boxen kopiert. Wenn es einer lokalen Variablen vom Typ zugeordnet wurde IFoo, Die Art dieser Variablen wäre jedoch "ein Object das implementiert IFoo", und so Boxen wäre erforderlich, dass dieser Punkt.

Es kann hilfreich sein, eine Statik zu definieren ExecF<T>(ref T thing) where T:I Methode, die dann die aufrufen könnte I.F() Methode auf thing. Eine solche Methode würde kein Boxen erfordern und alle Selbstmutationen respektieren, die von durchgeführt werden I.F().

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top