Was können Sie in MSIL tun, was Sie in C # oder VB.NET nicht tun können? [geschlossen]


165

Der gesamte in .NET-Sprachen geschriebene Code wird in MSIL kompiliert. Gibt es jedoch bestimmte Aufgaben / Vorgänge, die Sie nur direkt mit MSIL ausführen können?

Lassen Sie uns auch in MSIL einfacher arbeiten als in C #, VB.NET, F #, j # oder einer anderen .NET-Sprache.

Bisher haben wir Folgendes:

  1. Schwanzrekursion
  2. Generische Co / Contravariance
  3. Überladungen, die sich nur in Rückgabetypen unterscheiden
  4. Zugriffsmodifikatoren überschreiben
  5. Haben Sie eine Klasse, die nicht von System.Object erben kann
  6. Gefilterte Ausnahmen (können in vb.net durchgeführt werden)
  7. Aufruf einer virtuellen Methode des aktuellen statischen Klassentyps.
  8. Holen Sie sich einen Überblick über die Box-Version eines Werttyps.
  9. Mach einen Versuch / Fehler.
  10. Verwendung verbotener Namen.
  11. Definieren Sie Ihre eigenen parameterlosen Konstruktoren für Werttypen .
  12. Definieren Sie Ereignisse mit einem raiseElement.
  13. Einige Konvertierungen sind von der CLR zulässig, jedoch nicht von C #.
  14. Machen Sie eine Nicht- main()Methode als .entrypoint.
  15. Arbeiten Sie direkt mit den nativen intund nativen unsigned intTypen.
  16. Spielen Sie mit vorübergehenden Zeigern
  17. Emitbyte-Direktive in MethodBodyItem
  18. Nicht System.Exception-Typen werfen und abfangen
  19. Enumit Enums (Unverified)
  20. Sie können ein Array von Bytes als (4x kleineres) Array von Ints behandeln.
  21. Sie können ein Feld / eine Methode / eine Eigenschaft / ein Ereignis haben, die alle denselben Namen haben (nicht überprüft).
  22. Sie können von einem eigenen catch-Block zurück in einen try-Block verzweigen.
  23. Sie haben Zugriff auf den Famandassem-Zugriffsspezifizierer ( protected internalist Fam oder Assem).
  24. Direkter Zugriff auf die <Module>Klasse zum Definieren globaler Funktionen oder einen Modulinitialisierer.

17
Ausgezeichnete Frage!
Tamas Czinege

5
F # unterstützt die Schwanzrekursion, siehe: en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink

4
Aufzählungen erben? Das wäre manchmal so schön ..
Jimmy Hoffa

1
Die Hauptmethode hat ein Großbuchstaben M in .NET
Concrete Gannet

4
Die Behauptung "geschlossen als nicht konstruktiv" ist absurd. Dies ist eine empirische Frage.
Jim Balter

Antworten:


34

MSIL ermöglicht Überladungen, die sich aufgrund nur in Rückgabetypen unterscheiden

call void [mscorlib]System.Console::Write(string)

oder

callvirt int32 ...

5
Woher kennst du solche Sachen? :)
Gerrie Schenck


8
Das ist fantastisch. Wer wollte nicht Rücküberladungen machen?
Jimmy Hoffa

12
Wenn zwei Methoden bis auf den Rückgabetyp identisch sind, kann entweder von C # oder vb.net aufgerufen werden?
Supercat

29

Die meisten .NET-Sprachen, einschließlich C # und VB, verwenden die Funktion zur Endrekursion von MSIL-Code nicht.

Die Schwanzrekursion ist eine Optimierung, die in funktionalen Sprachen üblich ist. Es tritt auf, wenn eine Methode A endet, indem der Wert von Methode B zurückgegeben wird, sodass der Stapel von Methode A freigegeben werden kann, sobald der Aufruf von Methode B erfolgt ist.

MSIL-Code unterstützt die Schwanzrekursion explizit, und für einige Algorithmen könnte dies eine wichtige Optimierung sein. Da C # und VB die entsprechenden Anweisungen nicht generieren, muss dies manuell erfolgen (oder mit F # oder einer anderen Sprache).

Hier ist ein Beispiel dafür, wie die Schwanzrekursion manuell in C # implementiert werden kann:

private static int RecursiveMethod(int myParameter)
{
    // Body of recursive method
    if (BaseCase(details))
        return result;
    // ...

    return RecursiveMethod(modifiedParameter);
}

// Is transformed into:

private static int RecursiveMethod(int myParameter)
{
    while (true)
    {
        // Body of recursive method
        if (BaseCase(details))
            return result;
        // ...

        myParameter = modifiedParameter;
    }
}

Es ist üblich, die Rekursion zu entfernen, indem die lokalen Daten vom Hardwarestapel auf eine vom Heap zugewiesene Stapeldatenstruktur verschoben werden. Bei der oben gezeigten Eliminierung der Tail-Call-Rekursion wird der Stapel vollständig eliminiert, was eine ziemlich gute Optimierung darstellt. Außerdem muss der Rückgabewert keine lange Aufrufkette durchlaufen, sondern wird direkt zurückgegeben.

Trotzdem bietet die CIL diese Funktion als Teil der Sprache an, aber mit C # oder VB muss sie manuell implementiert werden. (Der Jitter kann diese Optimierung auch selbst vornehmen, aber das ist ein ganz anderes Problem.)


1
F # verwendet die Schwanzrekursion von MSIL nicht, da sie nur in vollständig vertrauenswürdigen Fällen (CAS) funktioniert, da kein Stapel zur Überprüfung auf Berechtigungszuweisungen (usw.) übrig bleibt.
Richard

10
Richard, ich bin mir nicht sicher, was du meinst. F # emittiert sicherlich den Schwanz. Anrufpräfix, so ziemlich überall. Untersuchen Sie die IL auf Folgendes: "let print x = print_any x".
MichaelGG

1
Ich glaube, dass die JIT in einigen Fällen ohnehin die Schwanzrekursion verwenden wird - und in einigen Fällen die explizite Anforderung dafür ignorieren wird. Dies hängt von der Prozessorarchitektur IIRC ab.
Jon Skeet

3
@Abel: Während die Prozessorarchitektur theoretisch irrelevant ist , ist sie in der Praxis nicht irrelevant , da die verschiedenen JITs für verschiedene Architekturen unterschiedliche Regeln für die Schwanzrekursion in .NET haben. Mit anderen Worten, Sie könnten sehr leicht ein Programm haben, das auf x86, aber nicht auf x64 explodierte. Nur weil Endrekursion kann in beiden Fällen implementiert werden , bedeutet nicht , es ist . Beachten Sie, dass es sich bei dieser Frage speziell um .NET handelt.
Jon Skeet

2
C # führt in bestimmten Fällen tatsächlich Tail Calls unter x64 durch: community.bartdesmet.net/blogs/bart/archive/2010/07/07/… .
Pieter van Ginkel

21

In MSIL können Sie eine Klasse haben, die nicht von System.Object erben kann.

Beispielcode: Kompilieren Sie ihn mit ilasm.exe. UPDATE: Sie müssen "/ NOAUTOINHERIT" verwenden, um zu verhindern, dass Assembler automatisch erben.

// Metadata version: v2.0.50215
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 2:0:0:0
}
.assembly sample
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module sample.exe
// MVID: {A224F460-A049-4A03-9E71-80A36DBBBCD3}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x02F20000


// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi beforefieldinit Hello
{
  .method public hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size       13 (0xd)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Hello World!"
    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000b:  nop
    IL_000c:  ret
  } // end of method Hello::Main
} // end of class Hello

2
@ Jon Skeet - Bei allem Respekt, könnten Sie mir bitte helfen zu verstehen, was NOAUTOINHERIT bedeutet. MSDN gibt an: "Deaktiviert die Standardvererbung von Object, wenn keine Basisklasse angegeben ist. Neu in .NET Framework Version 2.0."
Ramesh

2
@ Michael - Die Frage bezieht sich auf MSIL und nicht auf Common Intermediate Language. Ich bin damit einverstanden, dass dies in CIL möglicherweise nicht möglich ist, aber es funktioniert immer noch mit MSIL
Ramesh

4
@Ramesh: Ups, du hast absolut recht. Ich würde an diesem Punkt sagen, dass es die Standardspezifikation verletzt und nicht verwendet werden sollte. Der Reflektor lädt nicht einmal die Baugruppe. Es kann jedoch mit ilasm durchgeführt werden. Ich frage mich, warum um alles in der Welt es dort ist.
Jon Skeet

4
(Ah, ich sehe, dass das / noautoinherit-Bit nach meinem Kommentar hinzugefügt wurde. Zumindest fühle ich mich etwas besser, wenn ich es vorher nicht realisiere ...)
Jon Skeet

1
Ich werde hinzufügen, dass es zumindest unter .NET 4.5.2 unter Windows kompiliert, aber nicht ausgeführt wird ( TypeLoadException). PEVerify gibt Folgendes zurück: [MD]: Fehler: TypeDef, das keine Schnittstelle und nicht die Object-Klasse ist, erweitert das Null-Token.
Xanatos

20

Es ist möglich, die Modifikatoren protectedund zu kombinieren internal. Wenn Sie in C # schreiben, kann auf protected internalein Mitglied über die Assembly und über abgeleitete Klassen zugegriffen werden. Via MSIL können Sie ein Mitglied erhalten , die von abgeleiteten Klassen innerhalb der Baugruppe zugänglich ist nur . (Ich denke, das könnte ziemlich nützlich sein!)


4
Es ist ein Kandidat, der jetzt in C # 7.1 ( github.com/dotnet/csharplang/issues/37 ) implementiert werden soll. Der Zugriffsmodifikator lautetprivate protected
Happypig375,

5
Es wurde als Teil von C # 7.2 veröffentlicht: blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Joe Sewell

18

Oh, ich habe das damals nicht bemerkt. (Wenn Sie das Jon-Skeet-Tag hinzufügen, ist es wahrscheinlicher, aber ich überprüfe es nicht so oft.)

Es sieht so aus, als hätten Sie bereits ziemlich gute Antworten. Zusätzlich:

  • Sie können die Box-Version eines Werttyps in C # nicht in den Griff bekommen. Sie können in C ++ / CLI
  • Sie können in C # keinen Versuch / Fehler machen ("Fehler" ist wie "Alles fangen und am Ende des Blocks neu werfen" oder "Endlich, aber nur bei Fehler").
  • Es gibt viele Namen, die von C # verboten sind, aber legal IL
  • Mit IL können Sie Ihre eigenen parameterlosen Konstruktoren für Werttypen definieren .
  • Sie können keine Ereignisse mit einem "Raise" -Element in C # definieren. (In VB Sie haben zu für benutzerdefinierte Ereignisse, aber „default“ Ereignisse umfassen nicht ein.)
  • Einige Konvertierungen sind von der CLR zulässig, nicht jedoch von C #. Wenn Sie objectin C # übergehen, funktionieren diese manchmal. Ein Beispiel finden Sie in einer uint [] / int [] SO-Frage .

Ich werde das ergänzen, wenn mir noch etwas einfällt ...


3
Ah, der Jon-Skeet-Tag, ich wusste, dass mir etwas fehlt!
Binoj Antony

Um einen illegalen Bezeichnernamen zu verwenden, können Sie @ in C #
George Polevoy

4
@ George: Das funktioniert für Schlüsselwörter, aber nicht für alle gültigen IL-Namen. Versuchen Sie, <>aals Name in C # anzugeben ...
Jon Skeet


14

In IL können Sie jeden Typ werfen und fangen, nicht nur Typen, von denen abgeleitet wurde System.Exception.


6
Sie können dies auch in C # tun. Mit try/ catchohne Klammern in der catch-Anweisung werden auch nicht ausnahmeähnliche Ausnahmen abgefangen. Werfen ist jedoch nur möglich, wenn Sie von erben Exception.
Abel

@Abel Man kann kaum sagen, dass man etwas fängt, wenn man sich nicht darauf beziehen kann.
Jim Balter

2
@ JimBalter Wenn Sie es nicht fangen, stürzt die Anwendung ab. Wenn Sie es fangen, stürzt die Anwendung nicht ab. Das Verweisen auf das Ausnahmeobjekt unterscheidet sich also vom Abfangen.
Daniel Earwicker

Lol! Eine Unterscheidung zwischen Beenden oder Fortfahren der App ist Pedanterie? Jetzt glaube ich, ich habe vielleicht alles gehört.
Daniel Earwicker

Interessanterweise können Sie dies mit der CLR nicht mehr tun. Standardmäßig werden Nicht-Ausnahmeobjekte in RuntimeWrappedException eingeschlossen .
Jwosty

10

IL unterscheidet zwischen callund callvirtfür virtuelle Methodenaufrufe. Mit der ersteren können Sie den Aufruf einer virtuellen Methode des aktuellen statischen Klassentyps anstelle der virtuellen Funktion im dynamischen Klassentyp erzwingen .

C # hat keine Möglichkeit dazu:

abstract class Foo {
    public void F() {
        Console.WriteLine(ToString()); // Always a virtual call!
    }

    public override string ToString() { System.Diagnostics.Debug.Assert(false); }
};

sealed class Bar : Foo {
    public override string ToString() { return "I'm called!"; }
}

VB kann wie IL mithilfe der MyClass.Method()Syntax nicht- virtuelle Aufrufe ausgeben . Oben wäre dies MyClass.ToString().


9

In einem try / catch können Sie den try-Block von seinem eigenen catch-Block aus erneut eingeben. Sie können dies also tun:

.try {
    // ...

  MidTry:
    // ...

    leave.s RestOfMethod
}
catch [mscorlib]System.Exception {
    leave.s MidTry  // branching back into try block!
}

RestOfMethod:
    // ...

AFAIK Sie können dies nicht in C # oder VB tun


3
Ich kann sehen, warum dies weggelassen wurde - es hat einen deutlichen Geruch vonGOTO
Basic

3
Klingt ähnlich wie On Error Resume Next in VB.NET
Thomas Weller

1
Dies kann tatsächlich in VB.NET erfolgen. Die GoTo-Anweisung kann von Catchzu verzweigenTry . Führen Sie hier den Testcode online aus .
mbomb007

9

Mit IL und VB.NET können Sie Filter hinzufügen, wenn Ausnahmen abgefangen werden, aber C # v3 unterstützt diese Funktion nicht.

Dieses VB.NET-Beispiel stammt von http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx (beachten Sie das When ShouldCatch(ex) = Truein der Fangklausel):

Try
   Foo()
Catch ex As CustomBaseException When ShouldCatch(ex)
   Console.WriteLine("Caught exception!")
End Try

17
Bitte entfernen Sie das = True, es lässt meine Augen bluten!
Konrad Rudolph

Warum? Dies ist VB und nicht C #, daher gibt es keine = / == Probleme. ;-)
PeSHIr

Nun, c # kann "werfen;", so dass das gleiche Ergebnis erzielt werden kann.
Frank Schwieterman

8
paSHIr, ich glaube, er hat über die Redudanz davon gesprochen
LegendLength

5
@Frank Schwieterman: Es gibt einen Unterschied zwischen dem Fangen und erneuten Auslösen einer Ausnahme und dem Zurückhalten beim Fangen. Filter werden vor verschachtelten "finally" -Anweisungen ausgeführt, sodass die Umstände, die die Ausnahme verursacht haben, beim Ausführen des Filters weiterhin bestehen. Wenn man erwartet, dass eine signifikante Anzahl von SocketException ausgelöst wird, die man relativ leise abfangen möchte, aber einige von ihnen Probleme signalisieren, kann es sehr nützlich sein, den Zustand zu untersuchen, wenn eine problematische ausgelöst wird.
Supercat


7

Native types
Sie können direkt mit den Typen int und native int ohne Vorzeichen arbeiten (in c # können Sie nur mit einem IntPtr arbeiten, der nicht identisch ist.

Transient Pointers
Sie können mit vorübergehenden Zeigern spielen, bei denen es sich um Zeiger auf verwaltete Typen handelt, die sich jedoch garantiert nicht im Speicher bewegen, da sie sich nicht im verwalteten Heap befinden. Ich bin mir nicht ganz sicher, wie Sie dies sinnvoll nutzen können, ohne mit nicht verwaltetem Code herumzuspielen, aber es ist nicht direkt durch Dinge wie stackalloc den anderen Sprachen ausgesetzt.

<Module>
Sie können mit der Klasse herumspielen, wenn Sie dies wünschen (Sie können dies durch Nachdenken tun, ohne IL zu benötigen).

.emitbyte

15.4.1.1 Die .emitbyte-Direktive MethodBodyItem :: =… | .emitbyte Int32 Diese Anweisung bewirkt, dass ein vorzeichenloser 8-Bit-Wert an der Stelle, an der die Anweisung angezeigt wird, direkt in den CIL-Stream der Methode ausgegeben wird. [Hinweis: Die .emitbyte-Direktive wird zum Generieren von Tests verwendet. Es ist nicht erforderlich, um reguläre Programme zu generieren. Endnote]

.entrypoint
Sie haben etwas mehr Flexibilität, Sie können es beispielsweise auf Methoden anwenden, die nicht Main heißen.

Lesen Sie die Spezifikation. Ich bin sicher, Sie werden noch ein paar mehr finden.


+1, einige versteckte Edelsteine ​​hier. Beachten Sie, dass <Module>dies als spezielle Klasse für Sprachen gedacht ist, die globale Methoden akzeptieren (wie dies bei VB der Fall ist), C # jedoch nicht direkt darauf zugreifen kann.
Abel

Vorübergehende Zeiger scheinen ein sehr nützlicher Typ zu sein. Viele der Einwände gegen veränderbare Strukturen beruhen auf ihrer Unterlassung. Zum Beispiel "DictOfPoints (Schlüssel) .X = 5;" wäre praktikabel, wenn DictOfPoints (Schlüssel) einen vorübergehenden Zeiger auf eine Struktur zurückgeben würde, anstatt die Struktur nach Wert zu kopieren.
Supercat

@supercat, das mit einem vorübergehenden Zeiger nicht funktionieren würde, könnten sich die fraglichen Daten auf dem Heap befinden. Was Sie wollen, ist der Schiedsrichter,
ShuggyCoUk

@ ShuggyCoUk: Interessant. Ich hoffe wirklich, dass Eric überredet werden kann, ein Mittel bereitzustellen, mit dem "DictOfPoints (Schlüssel) .X = 5;" könnte zur Arbeit gebracht werden. Wenn man DictOfPoints fest codieren möchte, um ausschließlich mit dem Typ Point (oder einem anderen bestimmten Typ) zu arbeiten, kann man das fast zum Laufen bringen, aber es ist ein Schmerz. Übrigens, eine Sache, die ich gerne sehen würde, wäre ein Mittel, mit dem man eine offene generische Funktion schreiben könnte, z. B. DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...) könnte nach Bedarf erweitert werden. Ich würde vermuten, dass es in MSIL eine Möglichkeit gibt, dies zu tun. mit etwas Compiler-Hilfe ...
Supercat

@ShuggyCoUk: ... es würde eine andere Möglichkeit bieten, Eigenschaften nach Referenz zu haben, mit dem zusätzlichen Bonus, dass einige Eigenschaften ausgeführt werden könnten, nachdem alles, was Außenstehende mit der Referenz tun, getan wurde.
Supercat

6

Sie können die Co / Contra-Varianz von Hack-Methoden überschreiben, was C # nicht zulässt (dies ist NICHT dasselbe wie generische Varianz!). Ich habe hier und in Teil 1 und 2 weitere Informationen zur Implementierung


4

Ich denke, derjenige, den ich mir immer wieder gewünscht habe (aus völlig falschen Gründen), war die Vererbung in Enums. In SMIL scheint es nicht schwierig zu sein (da Enums nur Klassen sind), aber die C # -Syntax verlangt nicht, dass Sie dies tun.


4

Hier noch etwas mehr:

  1. Sie können zusätzliche Instanzmethoden in Delegaten haben.
  2. Delegierte können Schnittstellen implementieren.
  3. Sie können statische Mitglieder in Delegaten und Schnittstellen haben.

3

20) Sie können ein Array von Bytes als (4x kleineres) Array von Ints behandeln.

Ich habe dies kürzlich verwendet, um eine schnelle XOR-Implementierung durchzuführen, da die CLR-xor-Funktion mit Ints arbeitet und ich XOR für einen Bytestream ausführen musste.

Der resultierende Code ist ~ 10x schneller als der in C # durchgeführte Wert (XOR für jedes Byte).

===

Ich habe nicht genug Stackoverflow Street Credz, um die Frage zu bearbeiten und diese als # 20 zur Liste hinzuzufügen, wenn jemand anderes könnte, wäre das gut ;-)


3
Anstatt in IL einzutauchen, hätten Sie dies mit unsicheren Zeigern erreichen können. Ich würde mir vorstellen, dass es genauso schnell und vielleicht auch schneller gewesen wäre, da es keine Grenzüberprüfungen durchführen würde.
P Daddy

3

Etwas, das Verschleierer verwenden - Sie können ein Feld / eine Methode / eine Eigenschaft / ein Ereignis haben, die alle denselben Namen haben.


1
Ich habe ein Beispiel auf meiner Website veröffentlicht: jasonhaley.com/files/NameTestA.zip In dieser Zip gibt es die IL und eine Exe, die eine Klasse mit dem folgenden 'A' enthält: -Klassenname ist A -Event benannt A-Methode mit dem Namen A -Property mit dem Namen A -2 Felder mit dem Namen AI können keine gute Referenz finden, auf die Sie hinweisen können, obwohl ich sie wahrscheinlich entweder in der ecma 335-Spezifikation oder in Serge Lidins Buch gelesen habe.
Jason Haley

2

Enum-Vererbung ist nicht wirklich möglich:

Sie können von einer Enum-Klasse erben. Das Ergebnis verhält sich jedoch nicht wie eine Aufzählung. Es verhält sich nicht einmal wie ein Werttyp, sondern wie eine gewöhnliche Klasse. Die seltsame Sache ist: IsEnum: True, IsValueType: True, IsClass: False

Dies ist jedoch nicht besonders nützlich (es sei denn, Sie möchten eine Person oder die Laufzeit selbst verwirren.)


2

Sie können eine Klasse auch vom System.Multicast-Delegaten in IL ableiten, dies ist jedoch in C # nicht möglich:

// Die folgende Klassendefinition ist unzulässig:

öffentliche Klasse YourCustomDelegate: MulticastDelegate {}


1

Sie können in IL auch Methoden auf Modulebene (auch als globale Methoden bezeichnet) definieren, und in C # können Sie dagegen nur Methoden definieren, solange sie an mindestens einen Typ angehängt sind.

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.