Que pouvez-vous faire dans MSIL que vous ne pouvez pas faire en C # ou VB.NET? [fermé]

165

Tout le code écrit dans les langages .NET compile vers MSIL, mais y a-t-il des tâches / opérations spécifiques que vous ne pouvez effectuer qu'en utilisant directement MSIL?

Laissez-nous aussi faire les choses plus facilement dans MSIL que C #, VB.NET, F #, j # ou tout autre langage .NET.

Jusqu'à présent, nous avons ceci:

  1. Récursivité de la queue
  2. Co / Contravariance générique
  3. Surcharges qui ne diffèrent que par les types de retour
  4. Remplacer les modificateurs d'accès
  5. Avoir une classe qui ne peut pas hériter de System.Object
  6. Exceptions filtrées (peut être fait dans vb.net)
  7. Appel d'une méthode virtuelle du type de classe statique actuel.
  8. Obtenez une poignée sur la version encadrée d'un type valeur.
  9. Faites un essai / erreur.
  10. Utilisation de noms interdits.
  11. Définissez vos propres constructeurs sans paramètre pour les types valeur .
  12. Définissez des événements avec un raiseélément.
  13. Certaines conversions sont autorisées par le CLR mais pas par C #.
  14. Créez une main()méthode non comme .entrypoint.
  15. travailler directement avec les types natifs intet natifs unsigned int.
  16. Jouez avec des pointeurs transitoires
  17. emitbyte directive dans MethodBodyItem
  18. Lancer et intercepter les types non System.Exception
  19. Hériter des énumérations (non vérifiées)
  20. Vous pouvez traiter un tableau d'octets comme un tableau (4x plus petit) d'entiers.
  21. Vous pouvez avoir un champ / méthode / propriété / événement tous avoir le même nom (non vérifié).
  22. Vous pouvez revenir à un bloc try à partir de son propre bloc catch.
  23. Vous avez accès au spécificateur d'accès famandassem ( protected internalest fam ou assem)
  24. Accès direct à la <Module>classe pour définir des fonctions globales, ou un initialiseur de module.
Binoj Antony
la source
17
Excellente question!
Tamas Czinege
5
F # prend en charge la récursivité de queue voir: en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink
4
Hériter des énumérations? Ce serait tellement bien parfois ..
Jimmy Hoffa
1
La méthode Main a un M majuscule dans .NET
Concrete Gannet
4
L'affirmation «fermée comme non constructive» est absurde. C'est une question empirique.
Jim Balter

Réponses:

34

MSIL autorise les surcharges qui ne diffèrent que par les types de retour en raison de

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

ou

callvirt int32 ...
Anton Gogolev
la source
5
Comment savez-vous ce genre de choses? :)
Gerrie Schenck
8
C'est génial. Qui n'a pas voulu faire des surcharges de retour?
Jimmy Hoffa
12
Si deux méthodes sont identiques à l'exception du type de retour, peut-elle être appelée à partir de C # ou de vb.net?
supercat
29

La plupart des langages .Net, y compris C # et VB, n'utilisent pas la fonctionnalité de récursivité de queue du code MSIL.

La récursivité de la queue est une optimisation courante dans les langages fonctionnels. Cela se produit lorsqu'une méthode A se termine par renvoyer la valeur de la méthode B de sorte que la pile de la méthode A puisse être désallouée une fois l'appel à la méthode B effectué.

Le code MSIL prend en charge explicitement la récursivité de queue, et pour certains algorithmes, cela pourrait être une optimisation importante à faire. Mais comme C # et VB ne génèrent pas les instructions pour ce faire, cela doit être fait manuellement (ou en utilisant F # ou un autre langage).

Voici un exemple de la façon dont la récursivité de queue peut être implémentée manuellement en 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;
    }
}

Il est courant de supprimer la récursivité en déplaçant les données locales de la pile matérielle vers une structure de données de pile allouée au tas. Dans l'élimination de la récursivité des appels de queue comme indiqué ci-dessus, la pile est complètement éliminée, ce qui est une très bonne optimisation. De plus, la valeur de retour n'a pas à remonter une longue chaîne d'appels, mais elle est renvoyée directement.

Mais, de toute façon, le CIL fournit cette fonctionnalité dans le cadre du langage, mais avec C # ou VB, il doit être implémenté manuellement. (La gigue est également libre de faire cette optimisation elle-même, mais c'est un tout autre problème.)

Jeffrey L Whitledge
la source
1
F # n'utilise pas la récursivité de queue de MSIL, car il ne fonctionne que dans les cas entièrement fiables (CAS) en raison de la façon dont il ne laisse pas une pile pour vérifier les assrtions d'autorisation (etc.).
Richard
10
Richard, je ne suis pas sûr de ce que tu veux dire. F # émet certainement la queue. préfixe d'appel, à peu près partout. Examinez l'IL pour ceci: "let print x = print_any x".
MichaelGG
1
Je crois que le JIT utilisera de toute façon la récursivité de queue dans certains cas - et dans certains cas ignorera la demande explicite pour cela. Cela dépend de l'architecture du processeur, IIRC.
Jon Skeet le
3
@Abel: Bien que l'architecture du processeur ne soit pas pertinente en théorie , elle n'est pas sans importance en pratique car les différents JIT pour différentes architectures ont des règles différentes pour la récursivité de la queue dans .NET. En d'autres termes, vous pourriez très facilement avoir un programme qui a explosé sur x86 mais pas sur x64. Ce n'est pas parce que la récursivité de queue peut être implémentée dans les deux cas qu'elle l' est . Notez que cette question concerne spécifiquement .NET.
Jon Skeet
2
C # fait en fait des appels de queue sous x64 dans des cas spécifiques: community.bartdesmet.net/blogs/bart/archive/2010/07/07/… .
Pieter van Ginkel
21

Dans MSIL, vous pouvez avoir une classe qui ne peut pas hériter de System.Object.

Exemple de code: compilez-le avec ilasm.exe MISE À JOUR: Vous devez utiliser "/ NOAUTOINHERIT" pour empêcher l'assembleur d'hériter automatiquement.

// 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
Ramesh
la source
2
@Jon Skeet - Avec tout le respect que je vous dois, pourriez-vous s'il vous plaît m'aider à comprendre ce que signifie NOAUTOINHERIT. MSDN spécifie «Désactive l'héritage par défaut d'Object lorsqu'aucune classe de base n'est spécifiée. Nouveau dans le .NET Framework version 2.0».
Ramesh le
2
@Michael - La question concerne MSIL et non Common Intermediate Language. Je suis d'accord que cela n'est peut-être pas possible dans CIL, mais cela fonctionne toujours avec MSIL
Ramesh
4
@Ramesh: Oups, vous avez absolument raison. Je dirais qu'à ce stade, il enfreint les spécifications standard et ne devrait pas être utilisé. Le réflecteur ne charge même pas l'assemblage. Cependant, cela peut être fait avec un ilasme. Je me demande pourquoi diable il est là.
Jon Skeet le
4
(Ah, je vois que le bit / noautoinherit a été ajouté après mon commentaire. Au moins je me sens un peu mieux de ne pas m'en rendre compte avant ...)
Jon Skeet
1
J'ajouterai qu'au moins sur .NET 4.5.2 sous Windows, il compile mais n'exécute pas ( TypeLoadException). PEVerify renvoie: [MD]: Erreur: TypeDef qui n'est ni une interface ni la classe Object étend le jeton Nil.
xanatos
20

Il est possible de combiner les modificateurs d'accès protectedet internal. En C #, si vous écrivez protected internalun membre est accessible à partir de l'assembly et des classes dérivées. Via MSIL, vous pouvez obtenir un membre accessible uniquement à partir des classes dérivées de l'assembly . (Je pense que cela pourrait être très utile!)

yatima2975
la source
4
C'est un candidat maintenant pour être implémenté en C # 7.1 ( github.com/dotnet/csharplang/issues/37 ), le modificateur d'accès estprivate protected
Happypig375
5
Il a été publié dans le cadre de C # 7.2: blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Joe Sewell
18

Ooh, je n'ai pas remarqué ça à l'époque. (Si vous ajoutez la balise jon-skeet, c'est plus probable, mais je ne la vérifie pas souvent.)

Il semble que vous ayez déjà de très bonnes réponses. En outre:

  • Vous ne pouvez pas obtenir une poignée sur la version encadrée d'un type valeur en C #. Vous pouvez en C ++ / CLI
  • Vous ne pouvez pas faire un essai / défaut en C # ("fault" est comme un "attraper tout et relancer à la fin du bloc" ou "enfin mais seulement en cas d'échec")
  • Il y a beaucoup de noms qui sont interdits par C # mais IL légal
  • IL vous permet de définir vos propres constructeurs sans paramètre pour les types valeur .
  • Vous ne pouvez pas définir d'événements avec un élément "rise" en C #. (Dans VB, vous devez le faire pour les événements personnalisés, mais les événements "par défaut" n'en incluent pas.)
  • Certaines conversions sont autorisées par le CLR mais pas par C #. Si vous passez objecten C #, cela fonctionnera parfois. Voir une question SO uint [] / int [] pour un exemple.

J'ajouterai à cela si je pense à autre chose ...

Jon Skeet
la source
3
Ah le tag jon-skeet, je savais qu'il me manquait quelque chose!
Binoj Antony le
Pour utiliser un nom d'identifiant illégal, vous pouvez le préfixer avec @ en C #
George Polevoy
4
@George: Cela fonctionne pour les mots-clés, mais pas pour tous les noms IL valides. Essayez de spécifier <>acomme nom en C # ...
Jon Skeet
17

Le CLR prend déjà en charge la co / contravariance générique, mais C # n'obtient pas cette fonctionnalité avant la version 4.0

Ermau
la source
Pouvez-vous fournir un lien avec plus d'informations à ce sujet?
Binoj Antony le
14

En IL, vous pouvez lancer et attraper n'importe quel type, pas seulement les types dérivés de System.Exception.

Daniel Earwicker
la source
6
Vous pouvez également le faire en C #, avec try/ catchsans parenthèses dans l'instruction catch, vous attraperez également les exceptions qui ne ressemblent pas à des exceptions. Lancer, cependant, n'est en effet possible que lorsque vous héritez de Exception.
Abel
@Abel Vous pouvez difficilement dire que vous attrapez quelque chose si vous ne pouvez pas vous y référer.
Jim Balter
2
@JimBalter si vous ne l'attrapez pas, l'application plante. Si vous l'attrapez, l'application ne plante pas. Donc, se référer à l'objet d'exception est distinct de l'attraper.
Daniel Earwicker
Lol! Une distinction entre la fin ou la poursuite de l'application est-elle du pédantisme? Maintenant, je pense que j'ai peut-être tout entendu.
Daniel Earwicker le
Fait intéressant, le CLR ne vous permet plus de faire cela. Par défaut, il encapsulera les objets sans exception dans RuntimeWrappedException .
Jwosty
10

IL fait la distinction entre callet callvirtpour les appels de méthode virtuelle. En utilisant le premier, vous pouvez forcer l'appel d'une méthode virtuelle du type de classe statique actuel au lieu de la fonction virtuelle dans le type de classe dynamique.

C # n'a aucun moyen de faire cela:

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, comme IL, peut émettre des appels non virtuels en utilisant la MyClass.Method()syntaxe. Dans ce qui précède, ce serait MyClass.ToString().

Konrad Rudolph
la source
9

Dans un try / catch, vous pouvez ressaisir le bloc try à partir de son propre bloc catch. Donc, vous pouvez faire ceci:

.try {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

AFAIK vous ne pouvez pas faire cela en C # ou VB

thecoop
la source
3
Je ne vois pas pourquoi cela a été omis - Il a une odeur distincteGOTO
base
3
Sonne beaucoup comme On Error Resume Next dans VB.NET
Thomas Weller
1
Cela peut en fait être fait dans VB.NET. L'instruction GoTo peut passer de Catchà sonTry . Exécutez le code de test en ligne ici .
mbomb007
9

Avec IL et VB.NET, vous pouvez ajouter des filtres lors de la capture d'exceptions, mais C # v3 ne prend pas en charge cette fonctionnalité.

Cet exemple VB.NET est tiré de http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx (notez le When ShouldCatch(ex) = Truedans le Clause de capture):

Try
   Foo()
Catch ex As CustomBaseException When ShouldCatch(ex)
   Console.WriteLine("Caught exception!")
End Try
Emanuele Aina
la source
17
S'il vous plait, retirez le = True, ça me fait saigner les yeux!
Konrad Rudolph
Pourquoi? Ceci est VB et non C #, donc aucun problème = / == n'existe. ;-)
peSHIr
Eh bien, c # peut faire "throw;", donc le même résultat peut être obtenu.
Frank Schwieterman
8
paSHIr, je crois qu'il parlait de la redondance de celui
LegendLength
5
@Frank Schwieterman: Il y a une différence entre attraper et relancer une exception, et se retenir de l'attraper. Les filtres s'exécutent avant toute instruction imbriquée «finally», de sorte que les circonstances qui ont provoqué l'exception existent toujours lorsque le filtre est exécuté. Si l'on s'attend à ce qu'un nombre significatif d'exceptions SocketException soit lancé que l'on voudra attraper relativement silencieusement, mais quelques-unes d'entre elles signaleront des problèmes, pouvoir examiner l'état lorsqu'un problème problématique est lancé peut être très utile.
supercat du
7

Native types
Vous pouvez travailler directement avec les types int natif et int natif non signé (en c #, vous ne pouvez travailler que sur un IntPtr qui n'est pas le même.

Transient Pointers
Vous pouvez jouer avec des pointeurs transitoires, qui sont des pointeurs vers des types gérés mais garantis de ne pas bouger en mémoire car ils ne sont pas dans le tas géré. Vous ne savez pas exactement comment vous pourriez l'utiliser utilement sans jouer avec du code non managé, mais il n'est pas exposé aux autres langages directement uniquement via des choses comme stackalloc.

<Module>
vous pouvez jouer avec la classe si vous le souhaitez (vous pouvez le faire par réflexion sans avoir besoin d'IL)

.emitbyte

15.4.1.1 La directive .emitbyte MethodBodyItem :: =… | .emitbyte Int32 Cette directive entraîne l'émission d'une valeur 8 bits non signée directement dans le flux CIL de la méthode, à l'endroit où la directive apparaît. [Remarque: la directive .emitbyte est utilisée pour générer des tests. Il n'est pas nécessaire pour générer des programmes réguliers. note de fin]

.entrypoint
Vous avez un peu plus de flexibilité là-dessus, vous pouvez l'appliquer à des méthodes non appelées Main par exemple.

avoir une lecture de la spécification, je suis sûr que vous en trouverez quelques autres.

ShuggyCoUk
la source
+1, quelques joyaux cachés ici. Notez que <Module>c'est une classe spéciale pour les langages qui acceptent les méthodes globales (comme le fait VB), mais en effet, C # ne peut pas y accéder directement.
Abel
Les pointeurs transitoires semblent être un type très utile; la plupart des objections aux structures mutables découlent de leur omission. Par exemple, "DictOfPoints (clé) .X = 5;" serait réalisable si DictOfPoints (key) retournait un pointeur transitoire vers une structure, plutôt que de copier la structure par valeur.
supercat le
@supercat qui ne fonctionnerait pas avec un pointeur transitoire, les données en question pourraient être sur le tas. ce que vous voulez, c'est que l'arbitre renvoie le Eric parle ici: blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
ShuggyCoUk
@ShuggyCoUk: Intéressant. J'espère vraiment qu'Eric pourra être persuadé de fournir un moyen par lequel "DictOfPoints (key) .X = 5;" pourrait être mis en œuvre. À l'heure actuelle, si l'on veut coder en dur des DictOfPoints pour travailler exclusivement avec le type Point (ou un autre type particulier), on peut presque faire fonctionner cela, mais c'est pénible. BTW, une chose que j'aimerais voir serait un moyen par lequel on pourrait écrire une fonction générique ouverte, par exemple DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...) qui pourrait s'étendre au besoin. Je suppose qu'il y aurait un moyen de faire cela dans MSIL; avec de l'aide du compilateur ...
supercat
@ShuggyCoUk: ... cela fournirait une autre façon d'avoir des propriétés par référence, avec le bonus supplémentaire que certaines propriétés pourraient fonctionner après que tout ce que les étrangers feront à la référence ait été fait.
supercat
6

Vous pouvez pirater le remplacement de méthode co / contra-variance, ce que C # n'autorise pas (ce n'est PAS la même chose que la variance générique!). J'ai plus d'informations sur la mise en œuvre de ceci ici , et les parties 1 et 2

éco
la source
4

Je pense que celui que je souhaitais (avec des raisons totalement erronées) était l'héritage dans Enums. Cela ne semble pas difficile à faire dans SMIL (puisque les Enums ne sont que des classes) mais ce n'est pas quelque chose que la syntaxe C # veut que vous fassiez.

Shalom Craimer
la source
4

En voici encore plus:

  1. Vous pouvez avoir des méthodes d'instance supplémentaires dans les délégués.
  2. Les délégués peuvent implémenter des interfaces.
  3. Vous pouvez avoir des membres statiques dans les délégués et les interfaces.
leppie
la source
3

20) Vous pouvez traiter un tableau d'octets comme un tableau (4x plus petit) d'entiers.

Je l'ai utilisé récemment pour faire une implémentation rapide de XOR, car la fonction CLR xor fonctionne sur ints et j'avais besoin de faire XOR sur un flux d'octets.

Le code résultant s'est avéré être ~ 10x plus rapide que l'équivalent fait en C # (faisant XOR sur chaque octet).

===

Je n'ai pas assez de credz de rue stackoverflow pour modifier la question et l'ajouter à la liste en tant que # 20, si quelqu'un d'autre le pouvait, ce serait bien ;-)

Rosenfield
la source
3
Plutôt que de plonger dans IL, vous auriez pu accomplir cela avec des pointeurs dangereux. J'imagine que cela aurait été tout aussi rapide, et peut-être plus rapide, car cela ne ferait pas de vérification des limites.
P Daddy
3

Quelque chose que les obfuscateurs utilisent - vous pouvez avoir un champ / une méthode / une propriété / un événement ayant tous le même nom.

Jason Haley
la source
1
J'ai mis un échantillon sur mon site: jasonhaley.com/files/NameTestA.zip Dans ce zip, il y a l'IL et un exe qui contient une classe avec le même 'A': -class name est A -Event named A -Méthode nommée A -Property nommée A -2 Les champs nommés AI ne peuvent pas trouver une bonne référence vers laquelle vous pointer, bien que je l'ai probablement lu dans la spécification ecma 335 ou dans le livre de Serge Lidin.
Jason Haley
2

L'héritage enum n'est pas vraiment possible:

Vous pouvez hériter d'une classe Enum. Mais le résultat ne se comporte pas comme un Enum en particulier. Il ne se comporte même pas comme un type valeur, mais comme une classe ordinaire. La chose srange est: IsEnum: True, IsValueType: True, IsClass: False

Mais ce n'est pas particulièrement utile (sauf si vous voulez confondre une personne ou le moteur d'exécution lui-même).

Zotta
la source
2

Vous pouvez également dériver une classe du délégué System.Multicast en IL, mais vous ne pouvez pas le faire en C #:

// La définition de classe suivante est illégale:

classe publique YourCustomDelegate: MulticastDelegate {}

plaureano
la source
1

Vous pouvez également définir des méthodes au niveau du module (aka globales) dans IL, et C #, en revanche, ne vous permet de définir des méthodes que tant qu'elles sont attachées à au moins un type.

plaureano
la source