Cosa puoi fare in MSIL che non puoi fare in C # o VB.NET? [chiuso]


165

Tutto il codice scritto nei linguaggi .NET viene compilato in MSIL, ma esistono attività / operazioni specifiche che è possibile eseguire solo utilizzando MSIL direttamente?

Cerchiamo anche di fare le cose più facilmente in MSIL rispetto a C #, VB.NET, F #, j # o qualsiasi altro linguaggio .NET.

Finora abbiamo questo:

  1. Ricorsione della coda
  2. Co / Contravarianza generica
  3. Sovraccarichi che differiscono solo per i tipi di restituzione
  4. Sostituisci modificatori di accesso
  5. Avere una classe che non può ereditare da System.Object
  6. Eccezioni filtrate (può essere fatto in vb.net)
  7. Chiamata a un metodo virtuale del tipo di classe statica corrente.
  8. Ottieni un handle sulla versione in scatola di un tipo di valore.
  9. Fai un tentativo / guasto.
  10. Uso di nomi proibiti.
  11. Definisci i tuoi costruttori senza parametri per i tipi di valore .
  12. Definisci gli eventi con un raiseelemento.
  13. Alcune conversioni consentite dal CLR ma non da C #.
  14. Crea un main()metodo non come .entrypoint.
  15. lavorare direttamente con i tipi nativi inte nativi unsigned int.
  16. Gioca con puntatori transitori
  17. direttiva emitbyte in MethodBodyItem
  18. Lancia e cattura tipi non System.Exception
  19. Inherit Enums (Non verificato)
  20. È possibile trattare un array di byte come un array (4 volte più piccolo) di ints.
  21. Puoi avere un campo / metodo / proprietà / evento tutti con lo stesso nome (Non verificato).
  22. È possibile ramificare in un blocco try dal proprio blocco catch.
  23. Hai accesso allo specificatore di accesso famandassem ( protected internalè fam o assem)
  24. Accesso diretto alla <Module>classe per la definizione di funzioni globali o un inizializzatore del modulo.

17
Ottima domanda!
Tamas Czinege,

5
F # non supporta la ricorsione della coda vedi: en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink

4
Eredita enum? A volte sarebbe così bello ...
Jimmy Hoffa,

1
Il metodo principale ha la M maiuscola in .NET
Concrete Gannet,

4
L'affermazione "chiusa come non costruttiva" è assurda. Questa è una domanda empirica.
Jim Balter,

Risposte:


34

MSIL consente sovraccarichi che differiscono solo per i tipi di ritorno a causa di

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

o

callvirt int32 ...

5
Come conosci questo genere di cose? :)
Gerrie Schenck,


8
Questo e spettacolare. Chi non ha voluto fare sovraccarichi di ritorno?
Jimmy Hoffa,

12
Se due metodi sono identici ad eccezione del tipo restituito, possono essere chiamati da C # o vb.net?
supercat

29

La maggior parte dei linguaggi .Net, tra cui C # e VB, non utilizza la funzione di ricorsione della coda del codice MSIL.

La ricorsione della coda è un'ottimizzazione comune nei linguaggi funzionali. Si verifica quando un metodo A termina restituendo il valore del metodo B in modo tale che lo stack del metodo A possa essere deallocato una volta effettuata la chiamata al metodo B.

Il codice MSIL supporta esplicitamente la ricorsione della coda e per alcuni algoritmi potrebbe essere un'importante ottimizzazione da realizzare. Ma poiché C # e VB non generano le istruzioni per farlo, deve essere fatto manualmente (o usando F # o qualche altra lingua).

Ecco un esempio di come la ricorsione della coda può essere implementata manualmente in C #:

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

È pratica comune rimuovere la ricorsione spostando i dati locali dallo stack hardware su una struttura di dati dello stack allocata in heap. Nell'eliminazione della ricorsione in coda come mostrato sopra, lo stack viene eliminato completamente, il che è una buona ottimizzazione. Inoltre, il valore restituito non deve superare una lunga catena di chiamate, ma viene restituito direttamente.

Tuttavia, CIL fornisce questa funzionalità come parte del linguaggio, ma con C # o VB deve essere implementata manualmente. (Il jitter è anche libero di effettuare questa ottimizzazione da solo, ma questo è un altro problema.)


1
F # non utilizza la ricorsione della coda di MSIL, perché funziona solo in casi completamente attendibili (CAS) a causa del modo in cui non lascia uno stack per verificare le autorizzazioni (ecc.).
Richard,

10
Richard, non sono sicuro di cosa intendi. F # sicuramente emette la coda. chiama il prefisso, praticamente dappertutto. Esaminare IL per questo: "let print x = print_any x".
MichaelGG,

1
Credo che la SIC utilizzerà comunque la ricorsione della coda in alcuni casi - e in alcuni casi ignorerà la sua richiesta esplicita. Dipende dall'architettura del processore, IIRC.
Jon Skeet,

3
@Abel: Sebbene l'architettura del processore sia irrilevante in teoria , non è irrilevante nella pratica poiché i diversi JIT per architetture diverse hanno regole diverse per la ricorsione della coda in .NET. In altre parole, potresti facilmente avere un programma che è saltato in aria su x86 ma non su x64. Solo perché la ricorsione della coda può essere implementata in entrambi i casi non significa che lo sia . Si noti che questa domanda riguarda in particolare .NET.
Jon Skeet,

2
C # in realtà esegue le chiamate di coda in x64 in casi specifici: community.bartdesmet.net/blogs/bart/archive/2010/07/07/… .
Pieter van Ginkel,

21

In MSIL, puoi avere una classe che non può ereditare da System.Object.

Codice di esempio: compilarlo con ilasm.exe AGGIORNAMENTO: È necessario utilizzare "/ NOAUTOINHERIT" per impedire che l'assemblatore erediti automaticamente.

// 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 - Con tutto il rispetto, potresti aiutarmi a capire cosa significa NOAUTOINHERIT. MSDN specifica "Disabilita l'ereditarietà predefinita dall'oggetto quando non viene specificata alcuna classe base. Novità in .NET Framework versione 2.0".
Ramesh,

2
@Michael - La domanda riguarda MSIL e non Common Intermediate Language. Sono d'accordo che questo potrebbe non essere possibile in CIL ma, funziona ancora con MSIL
Ramesh,

4
@Ramesh: Oops, hai assolutamente ragione. Direi che a quel punto rompe le specifiche standard e non dovrebbe essere usato. Il riflettore non carica nemmeno il tutto. Tuttavia, può essere fatto con ilasm. Mi chiedo perché mai ci sia.
Jon Skeet,

4
(Ah, vedo che il bit / noautoinherit è stato aggiunto dopo il mio commento. Almeno mi sento un po 'meglio a non accorgermene prima ...)
Jon Skeet,

1
Aggiungerò che almeno su .NET 4.5.2 su Windows si compila ma non esegue ( TypeLoadException). PEVerify restituisce: [MD]: errore: TypeDef che non è un'interfaccia e non la classe Object estende il token Nil.
xanatos,

20

È possibile combinare i modificatori protectede internalaccedere. In C #, se si scrive protected internalun membro è accessibile dall'assembly e dalle classi derivate. Via MSIL è possibile ottenere un membro che è accessibile da classi derivate all'interno del gruppo unico . (Penso che potrebbe essere abbastanza utile!)


4
Ora è un candidato da implementare in C # 7.1 ( github.com/dotnet/csharplang/issues/37 ), il modificatore di accesso èprivate protected
Happypig375,

5
È stato rilasciato come parte di C # 7.2: blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Joe Sewell,

18

Ooh, non l'ho notato al momento. (Se aggiungi il tag jon-skeet è più probabile, ma non lo controllo così spesso.)

Sembra che tu abbia già abbastanza buone risposte. Inoltre:

  • Non è possibile ottenere un handle sulla versione in box di un tipo di valore in C #. È possibile in C ++ / CLI
  • Non puoi fare un tentativo / errore in C # ("errore" è come un "prendi tutto e ripeti alla fine del blocco" o "finalmente ma solo in caso di fallimento")
  • Ci sono molti nomi proibiti da C # ma IL legale
  • IL consente di definire i propri costruttori senza parametri per i tipi di valore .
  • Non è possibile definire eventi con un elemento "raise" in C #. (In VB si dispone per eventi personalizzati, ma gli eventi "predefiniti" non ne includono uno.)
  • Alcune conversioni sono consentite dal CLR ma non da C #. Se vai objectin C #, a volte funzioneranno. Vedi una domanda SO uint [] / int [] per un esempio.

Aggiungerò a questo se penso ad altro ...


3
Ah, il tag jon-skeet, sapevo che mi mancava qualcosa!
Binoj Antony,

Per usare un nome identificativo illegale, puoi prefissarlo con @ in C #
George Polevoy il

4
@George: funziona per le parole chiave, ma non per tutti i nomi IL validi. Prova a specificare <>acome nome in C # ...
Jon Skeet,


14

In IL puoi lanciare e catturare qualsiasi tipo, non solo i tipi derivati System.Exception.


6
Puoi farlo anche in C #, con try/ catchsenza parentesi nell'istruzione catch otterrai anche eccezioni non di eccezione. Lanciare, tuttavia, è effettivamente possibile solo quando si eredita Exception.
Abel,

@Abel Difficilmente puoi dire che stai prendendo qualcosa se non riesci a riferirti ad esso.
Jim Balter,

2
@JimBalter se non lo prendi, l'applicazione si blocca. Se lo prendi, l'applicazione non si arresta in modo anomalo. Quindi fare riferimento all'oggetto eccezione è distinto dal catturarlo.
Daniel Earwicker,

Lol! Una distinzione tra terminazione o proseguimento dell'app è la pedanteria? Ora penso di aver sentito tutto.
Daniel Earwicker,

È interessante notare che il CLR non ti consente più di farlo. Per impostazione predefinita, avvolgerà gli oggetti non di eccezione in RuntimeWrappedException .
Jwosty,

10

IL ha la distinzione tra calle callvirtper chiamate di metodo virtuali. Utilizzando il primo è possibile forzare la chiamata di un metodo virtuale del tipo di classe statica corrente anziché della funzione virtuale nel tipo di classe dinamica.

C # non ha modo di farlo:

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, come IL, può effettuare chiamate non virtuali utilizzando la MyClass.Method()sintassi. In quanto sopra, questo sarebbe MyClass.ToString().


9

In un tentativo / cattura, è possibile immettere nuovamente il blocco try dal proprio blocco catch. Quindi, puoi farlo:

.try {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

AFAIK non puoi farlo in C # o VB


3
Vedo perché questo è stato omesso - Ha un odore distinto diGOTO
Basic

3
Sembra molto simile a On Error Resume Next in VB.NET
Thomas Weller,

1
Questo può effettivamente essere fatto in VB.NET. L'istruzione GoTo può diramare da Catcha suaTry . Esegui il codice di test online qui .
mbomb007,

9

Con IL e VB.NET è possibile aggiungere filtri quando si rilevano eccezioni, ma C # v3 non supporta questa funzione.

Questo esempio di VB.NET è tratto da http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx (nota When ShouldCatch(ex) = Truenella Clausola di cattura):

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

17
Per favore, rimuovi il = True, mi sta facendo sanguinare gli occhi!
Konrad Rudolph,

Perché? Questo è VB e non C #, quindi non esistono problemi = / ==. ;-)
peSHIr

Bene, c # può fare "lancio;", quindi si può ottenere lo stesso risultato.
Frank Schwieterman,

8
Padre, credo che ne stesse parlando della ridondanza
LegendLength,

5
@Frank Schwieterman: c'è una differenza tra la cattura e la riproposizione di un'eccezione, invece di trattenerla. I filtri vengono eseguiti prima di qualsiasi istruzione "finally" nidificata, quindi le circostanze che hanno causato l'eccezione continueranno a esistere quando viene eseguito il filtro. Se ci si aspetta che venga lanciato un numero significativo di SocketException, si vorrà catturare in modo relativamente silenzioso, ma alcuni di loro segnaleranno problemi, essere in grado di esaminare lo stato quando viene lanciato un problema può essere molto utile.
supercat


7

Native types
Puoi lavorare direttamente con i tipi int nativi e int intatti senza segno (in c # puoi lavorare solo su un IntPtr che non è lo stesso.

Transient Pointers
È possibile giocare con puntatori temporanei, che sono puntatori a tipi gestiti ma che non possono spostarsi in memoria poiché non si trovano nell'heap gestito. Non sono del tutto sicuro di come si possa utilmente usare questo senza fare confusione con codice non gestito, ma non è esposto alle altre lingue direttamente solo attraverso cose come stackalloc.

<Module>
puoi fare casino con la classe se lo desideri (puoi farlo riflettendo senza bisogno di IL)

.emitbyte

15.4.1.1 La direttiva .emitbyte MethodBodyItem :: =… | .emitbyte Int32 Questa direttiva provoca l'emissione di un valore senza segno a 8 bit direttamente nel flusso CIL del metodo, nel punto in cui appare la direttiva. [Nota: la direttiva .emitbyte viene utilizzata per generare test. Non è necessario per generare programmi regolari. nota finale]

.entrypoint
Hai un po 'più di flessibilità su questo, puoi applicarlo a metodi non chiamati Main per esempio.

leggi le specifiche Sono sicuro che ne troverai altre.


+1, alcune gemme nascoste qui. Nota che <Module>si intende come classe speciale per i linguaggi che accettano metodi globali (come fa VB), ma in effetti C # non può accedervi direttamente.
Abel,

I puntatori transitori sembrano essere un tipo molto utile; molte delle obiezioni alle strutture mutabili derivano dalla loro omissione. Ad esempio, "DictOfPoints (chiave) .X = 5;" sarebbe praticabile se DictOfPoints (chiave) restituisse un puntatore temporaneo a una struttura, anziché copiare la struttura in base al valore.
supercat

@supercat che non funzionerebbe con un puntatore temporaneo, i dati in questione potrebbero essere nell'heap. quello che vuoi è che l'arbitro ritorni di cui parla Eric qui: blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
ShuggyCoUk

@ShuggyCoUk: interessante. Spero davvero che Eric possa essere convinto a fornire un mezzo con cui "DictOfPoints (key) .X = 5;" potrebbe essere fatto funzionare. In questo momento, se si vuole codificare DictOfPoints per funzionare esclusivamente con il tipo Point (o qualche altro tipo particolare), si può quasi farlo funzionare, ma è una seccatura. A proposito, una cosa che mi piacerebbe vedere sarebbe un mezzo con cui si potrebbe scrivere una funzione generica aperta ad es. DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...) che potrebbe espandersi secondo necessità. Immagino che ci sarebbe un modo per farlo in MSIL; con l'aiuto del compilatore ...
supercat

@ShuggyCoUk: ... fornirebbe un altro modo di avere proprietà per riferimento, con l'ulteriore vantaggio che alcune proprietà potrebbero eseguire dopo che è stato fatto tutto ciò che gli estranei faranno al riferimento.
supercat

6

Puoi hackerare il metodo ignorando la co / contro-varianza, cosa che C # non consente (questo NON è lo stesso della varianza generica!). Ho maggiori informazioni sull'implementazione qui e parti 1 e 2


4

Penso che quello che continuavo a desiderare (con ragioni completamente sbagliate) fosse l'eredità in Enums. Non sembra una cosa difficile da fare in SMIL (dato che gli Enum sono solo classi) ma non è qualcosa che la sintassi di C # vuole che tu faccia.


4

Eccone alcuni di più:

  1. È possibile disporre di metodi di istanza aggiuntivi nei delegati.
  2. I delegati possono implementare interfacce.
  3. È possibile avere membri statici in delegati e interfacce.

3

20) È possibile trattare un array di byte come un array (4x più piccolo) di ints.

L'ho usato di recente per eseguire un'implementazione XOR rapida, poiché la funzione xor CLR opera su ints e avevo bisogno di eseguire XOR su un flusso di byte.

Il codice risultante misurato è ~ 10 volte più veloce dell'equivalente fatto in C # (facendo XOR su ogni byte).

===

Non ho abbastanza credz di stackoverflow street per modificare la domanda e aggiungerla all'elenco come # 20, se qualcun altro potesse farlo sarebbe gonfio ;-)


3
Invece di immergerti in IL, avresti potuto farlo con puntatori non sicuri. Immagino che sarebbe stato altrettanto veloce, e forse più veloce, dal momento che non avrebbe controllato i limiti.
P Daddy,

3

Qualcosa che gli offuscatori usano - puoi avere un campo / metodo / proprietà / evento tutti hanno lo stesso nome.


1
Ho messo un campione sul mio sito: jasonhaley.com/files/NameTestA.zip In quella zip c'è l'IL e un exe che contiene una classe con la seguente 'A': -class name is A -Event name A -Method di nome A -Property di nome A -2 I campi di nome AI non riescono a trovare un buon riferimento per indicarti, anche se probabilmente l'ho letto nelle specifiche ecma 335 o nel libro di Serge Lidin.
Jason Haley,

2

L'eredità Enum non è davvero possibile:

È possibile ereditare da una classe Enum. Ma il risultato non si comporta come un Enum in particolare. Non si comporta nemmeno come un tipo di valore, ma come una normale classe. La cosa srange è: IsEnum: True, IsValueType: True, IsClass: False

Ma questo non è particolarmente utile (a meno che tu non voglia confondere una persona o l'autonomia stessa).


2

Puoi anche derivare una classe dal delegato System.Multicast in IL, ma non puoi farlo in C #:

// La seguente definizione di classe è illegale:

public class YourCustomDelegate: MulticastDelegate {}


1

Puoi anche definire metodi a livello di modulo (aka globali) in IL e C #, al contrario, ti consente di definire metodi solo se sono collegati ad almeno un tipo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.