¿Qué puede hacer en MSIL que no puede hacer en C # o VB.NET? [cerrado]

165

Todo el código escrito en lenguajes .NET se compila en MSIL, pero ¿hay tareas / operaciones específicas que solo puede hacer usando MSIL directamente?

Hagamos también que las cosas se hagan más fácilmente en MSIL que C #, VB.NET, F #, j # o cualquier otro lenguaje .NET.

Hasta ahora tenemos esto:

  1. Recursividad de la cola
  2. Co / Contravarianza Genérica
  3. Sobrecargas que difieren solo en los tipos de retorno
  4. Anular modificadores de acceso
  5. Tener una clase que no pueda heredar de System.Object
  6. Excepciones filtradas (se puede hacer en vb.net)
  7. Llamando a un método virtual del tipo de clase estática actual.
  8. Obtenga un identificador de la versión en caja de un tipo de valor.
  9. Haz un intento / falla.
  10. Uso de nombres prohibidos.
  11. Defina sus propios constructores sin parámetros para los tipos de valor .
  12. Definir eventos con un raiseelemento.
  13. Algunas conversiones permitidas por el CLR pero no por C #.
  14. Hacer un main()método no como el .entrypoint.
  15. trabajar con los tipos nativos inty nativos unsigned intdirectamente.
  16. Juega con punteros transitorios.
  17. directiva emitbyte en MethodBodyItem
  18. Lanza y atrapa tipos que no sean del sistema.
  19. Heredar enumeraciones (sin verificar)
  20. Puede tratar una matriz de bytes como una matriz (4x más pequeña) de entradas.
  21. Puede tener un campo / método / propiedad / evento, todos tienen el mismo nombre (sin verificar).
  22. Puede volver a ramificarse en un bloque try desde su propio bloque catch.
  23. Tiene acceso al especificador de acceso famandassem ( protected internales fam o assem)
  24. Acceso directo a la <Module>clase para definir funciones globales, o un inicializador de módulo.
Binoj Antony
fuente
17
Excelente pregunta!
Tamas Czinege
55
F # admite la recursividad de cola, consulte: en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink
44
¿Heredar enumeraciones? Eso sería muy agradable a veces ..
Jimmy Hoffa
1
El método principal tiene una M mayúscula en .NET
Concrete Gannet
44
El reclamo "cerrado como no constructivo" es absurdo. Ésta es una pregunta empírica.
Jim Balter

Respuestas:

34

MSIL permite sobrecargas que difieren solo en los tipos de retorno debido a

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

o

callvirt int32 ...
Anton Gogolev
fuente
55
¿Cómo sabes este tipo de cosas? :)
Gerrie Schenck
8
Esto es asombroso ¿Quién no ha querido hacer sobrecargas de devolución?
Jimmy Hoffa
12
Si dos métodos son idénticos excepto por el tipo de retorno, ¿se puede llamar desde C # o vb.net?
supercat
29

La mayoría de los lenguajes .Net, incluidos C # y VB, no utilizan la función de recursión de cola del código MSIL.

La recursividad de cola es una optimización que es común en lenguajes funcionales. Ocurre cuando un método A termina devolviendo el valor del método B de tal manera que la pila del método A se puede desasignar una vez que se realiza la llamada al método B.

El código MSIL admite la recursión de cola explícitamente, y para algunos algoritmos podría ser una optimización importante. Pero dado que C # y VB no generan las instrucciones para hacerlo, debe hacerse manualmente (o usando F # o algún otro lenguaje).

Aquí hay un ejemplo de cómo la recursividad de cola puede implementarse manualmente 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;
    }
}

Es una práctica común eliminar la recursividad moviendo los datos locales de la pila de hardware a una estructura de datos de pila asignada en el montón. En la eliminación de recursión de cola como se muestra arriba, la pila se elimina por completo, lo cual es una optimización bastante buena. Además, el valor de retorno no tiene que recorrer una larga cadena de llamadas, sino que se devuelve directamente.

Pero, de todos modos, el CIL proporciona esta característica como parte del lenguaje, pero con C # o VB debe implementarse manualmente. (El jitter también es libre de hacer esta optimización por sí solo, pero ese es un problema completamente diferente).

Jeffrey L Whitledge
fuente
1
F # no usa la recursión de cola de MSIL, porque solo funciona en casos de plena confianza (CAS) debido a la forma en que no deja una pila para verificar las asignaciones de permisos (etc.).
Richard
10
Richard, no estoy seguro de lo que quieres decir. F # ciertamente emite la cola. prefijo de llamada, prácticamente en todo el lugar. Examine el IL para esto: "let print x = print_any x".
MichaelGG
1
Creo que el JIT usará la recursión de cola de todos modos en algunos casos, y en algunos casos ignorará la solicitud explícita de la misma. Depende de la arquitectura del procesador, IIRC.
Jon Skeet
3
@Abel: Si bien la arquitectura del procesador es irrelevante en teoría , no es irrelevante en la práctica, ya que los diferentes JIT para diferentes arquitecturas tienen diferentes reglas para la recursividad de cola en .NET. En otras palabras, podría tener fácilmente un programa que explotó en x86 pero no en x64. El hecho de que la recursión de la cola se pueda implementar en ambos casos no significa que sea ​​así . Tenga en cuenta que esta pregunta es sobre .NET específicamente.
Jon Skeet
2
C # realmente hace llamadas de cola bajo x64 en casos específicos: community.bartdesmet.net/blogs/bart/archive/2010/07/07/… .
Pieter van Ginkel
21

En MSIL, puede tener una clase que no puede heredar de System.Object.

Código de muestra: compílelo con ilasm.exe ACTUALIZACIÓN: debe usar "/ NOAUTOINHERIT" para evitar que el ensamblador se herede automáticamente.

// 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
fuente
2
@ Jon Skeet: con el debido respeto, ¿podría ayudarme a comprender lo que significa NOAUTOINHERIT? MSDN especifica "Deshabilita la herencia predeterminada de Object cuando no se especifica una clase base. Nuevo en .NET Framework versión 2.0".
Ramesh
2
@Michael: la pregunta se refiere al MSIL y no al lenguaje intermedio común. Estoy de acuerdo en que esto puede no ser posible en CIL, pero aún funciona con MSIL
Ramesh
44
@Ramesh: Vaya, tienes toda la razón. Diría que en ese momento rompe la especificación estándar, y no debería usarse. Reflector ni siquiera carga el asmemente. Sin embargo, se puede hacer con ilasm. Me pregunto por qué demonios está ahí.
Jon Skeet
44
(Ah, veo que se agregó el bit / noautoinherit después de mi comentario. Al menos me siento un poco mejor por no darme cuenta antes ...)
Jon Skeet
1
Agregaré que al menos en .NET 4.5.2 en Windows se compila pero no se ejecuta ( TypeLoadException). PEVerify devuelve: [MD]: Error: TypeDef que no es una interfaz y que la clase Object no extiende el token Nil.
xanatos
20

Es posible combinar los modificadores protectedy internalacceder. En C #, si escribe protected internal, se puede acceder a un miembro desde el ensamblado y desde las clases derivadas. A través de MSIL puede obtener un elemento que se puede acceder desde las clases derivadas dentro del conjunto única . (¡Creo que podría ser muy útil!)

yatima2975
fuente
44
Es un candidato que ahora se implementará en C # 7.1 ( github.com/dotnet/csharplang/issues/37 ), el modificador de acceso esprivate protected
Happypig375
55
Ha sido lanzado como parte de C # 7.2: blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Joe Sewell
18

Ooh, no vi esto en ese momento. (Si agrega la etiqueta jon-skeet, es más probable, pero no la reviso con tanta frecuencia).

Parece que ya tienes respuestas bastante buenas. Adicionalmente:

  • No puede obtener un identificador de la versión en caja de un tipo de valor en C #. Puedes en C ++ / CLI
  • No puede hacer una prueba / falla en C # ("falla" es como "atrapar todo y volver a lanzar al final del bloque" o "finalmente, pero solo en caso de falla")
  • Hay muchos nombres que están prohibidos por C # pero IL legales
  • IL le permite definir sus propios constructores sin parámetros para los tipos de valor .
  • No puede definir eventos con un elemento "raise" en C #. (En VB se tiene que para los eventos personalizados, pero los acontecimientos "por defecto" no se incluye uno).
  • Algunas conversiones están permitidas por CLR pero no por C #. Si pasa por objectC #, a veces funcionará. Vea una pregunta uint [] / int [] SO para ver un ejemplo.

Agregaré a esto si pienso en otra cosa ...

Jon Skeet
fuente
3
Ah, la etiqueta jon-skeet, ¡sabía que me faltaba algo!
Binoj Antony
Para usar un nombre de identificador ilegal, puede
agregarle el
44
@George: Eso funciona para palabras clave, pero no para todos los nombres IL válidos. Intente especificar <>acomo un nombre en C # ...
Jon Skeet
14

En IL puedes lanzar y atrapar cualquier tipo, no solo los tipos derivados System.Exception.

Daniel Earwicker
fuente
66
También puede hacer eso en C #, con try/ catchsin paréntesis en la declaración catch, también detectará excepciones que no sean de excepción. Lanzar, sin embargo, solo es posible cuando heredas de Exception.
Abel
@Abel No puedes decir que estás atrapando algo si no puedes referirte a él.
Jim Balter
2
@JimBalter si no lo atrapa, la aplicación se bloquea. Si lo atrapa, la aplicación no se bloquea. Entonces, referirse al objeto de excepción es distinto de atraparlo.
Daniel Earwicker
Jajaja ¿Una distinción entre la finalización o continuación de la aplicación es pedantería? Ahora creo que puedo haber escuchado todo.
Daniel Earwicker
Curiosamente, el CLR ya no te permite hacer esto. Por defecto, envolverá objetos no excepcionales en RuntimeWrappedException .
Jwosty
10

IL tiene la distinción entre cally callvirtpara llamadas a métodos virtuales. Al usar el primero, puede forzar la llamada a un método virtual del tipo de clase estática actual en lugar de la función virtual en el tipo de clase dinámica.

C # no tiene forma de hacer esto:

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, como IL, puede emitir llamadas no virtuales mediante el uso de la MyClass.Method()sintaxis. En lo anterior, esto sería MyClass.ToString().

Konrad Rudolph
fuente
9

En un try / catch, puede volver a ingresar el bloque try desde su propio bloque catch. Entonces, puedes hacer esto:

.try {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

AFAIK no puedes hacer esto en C # o VB

thecoop
fuente
3
Puedo ver por qué esto se omitió. Tiene un olor característicoGOTO
Básico
3
Suena muy parecido a On Error Resume Next en VB.NET
Thomas Weller
1
Esto realmente se puede hacer en VB.NET. La instrucción GoTo puede ramificarse de Catcha suTry . Ejecute el código de prueba en línea aquí .
mbomb007
9

Con IL y VB.NET puede agregar filtros al detectar excepciones, pero C # v3 no admite esta función.

Este ejemplo de VB.NET está tomado de http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx (tenga When ShouldCatch(ex) = Trueen cuenta Cláusula de captura):

Try
   Foo()
Catch ex As CustomBaseException When ShouldCatch(ex)
   Console.WriteLine("Caught exception!")
End Try
Emanuele Aina
fuente
17
Por favor, retire = True, ¡me está haciendo sangrar los ojos!
Konrad Rudolph
¿Por qué? Esto es VB y no C #, por lo que no existen problemas = / ==. ;-)
peSHIr
Bueno, c # puede hacer "throw;", por lo que se puede lograr el mismo resultado.
Frank Schwieterman
8
paSHIr, creo que estaba hablando de la reducción de la misma
LegendLength
55
@Frank Schwieterman: Hay una diferencia entre atrapar y volver a lanzar una excepción, en lugar de esperar para atraparla. Los filtros se ejecutan antes de cualquier declaración "finalmente" anidada, por lo que las circunstancias que causaron la excepción seguirán existiendo cuando se ejecute el filtro. Si se espera que se produzca un número significativo de SocketException, querrá capturarlo de manera relativamente silenciosa, pero algunos de ellos señalarán problemas, ser capaz de examinar el estado cuando se produce uno problemático puede ser muy útil.
supercat
7

Native types
Puede trabajar directamente con los tipos int nativo e int sin signo nativo (en c # solo puede trabajar en un IntPtr que no es lo mismo.

Transient Pointers
Puede jugar con punteros transitorios, que son punteros a tipos administrados pero garantizados para no moverse en la memoria ya que no están en el montón administrado. No estoy completamente seguro de cómo podría usar esto de manera útil sin jugar con el código no administrado, pero no está expuesto a los otros idiomas directamente solo a través de cosas como stackalloc.

<Module>
puede meterse con la clase si así lo desea (puede hacerlo reflexionando sin necesidad de IL)

.emitbyte

15.4.1.1 La directiva .emitbyte MethodBodyItem :: =… | .emitbyte Int32 Esta directiva hace que se emita un valor de 8 bits sin signo directamente en el flujo CIL del método, en el punto en el que aparece la directiva. [Nota: la directiva .emitbyte se utiliza para generar pruebas. No es necesario para generar programas regulares. nota final]

.entrypoint
Tiene un poco más de flexibilidad en esto, puede aplicarlo a métodos que no se llaman Main, por ejemplo.

lea las especificaciones , estoy seguro de que encontrará algunas más.

ShuggyCoUk
fuente
+1, algunas gemas escondidas aquí. Tenga en cuenta que <Module>se entiende como una clase especial para lenguajes que aceptan métodos globales (como lo hace VB), pero de hecho, C # no puede acceder a él directamente.
Abel
Los punteros transitorios parecen ser un tipo muy útil; Muchas de las objeciones a las estructuras mutables se derivan de su omisión. Por ejemplo, "DictOfPoints (clave) .X = 5;" sería viable si DictOfPoints (clave) devolviera un puntero transitorio a una estructura, en lugar de copiar la estructura por valor.
supercat
@supercat que no funcionaría con un puntero transitorio, los datos en cuestión podrían estar en el montón. lo que quieres es que el árbitro regrese de lo que Eric habla aquí: blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
ShuggyCoUk
@ShuggyCoUk: Interesante. Realmente espero que Eric pueda ser persuadido para proporcionar un medio por el cual "DictOfPoints (clave) .X = 5;" podría hacerse trabajar. En este momento, si uno quiere codificar DictOfPoints para trabajar exclusivamente con type Point (o algún otro tipo en particular), uno casi puede hacer que funcione, pero es un dolor. Por cierto, una cosa que me gustaría ver sería un medio por el cual uno podría escribir una función genérica abierta, por ejemplo, DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...) que podría expandirse según sea necesario. Supongo que habría alguna forma de hacerlo en MSIL; con algo de ayuda del compilador ...
supercat
@ShuggyCoUk: ... proporcionaría otra forma de tener propiedades de referencia, con la ventaja adicional de que algunas propiedades podrían ejecutarse después de que se haya hecho todo lo que los extraños harán a la referencia.
supercat
6

Puede hackear la anulación de método co / contra-varianza, que C # no permite (¡esto NO es lo mismo que la varianza genérica!). Tengo más información sobre cómo implementar esto aquí , y las partes 1 y 2

thecoop
fuente
4

Creo que lo que seguía deseando (con razones completamente equivocadas) era la herencia en Enums. No parece algo difícil de hacer en SMIL (ya que las enumeraciones son solo clases), pero no es algo que la sintaxis de C # quiera que hagas.

Shalom Craimer
fuente
4

Aquí hay más:

  1. Puede tener métodos de instancia adicionales en los delegados.
  2. Los delegados pueden implementar interfaces.
  3. Puede tener miembros estáticos en delegados e interfaces.
leppie
fuente
3

20) Puede tratar una matriz de bytes como una matriz (4x más pequeña) de entradas.

Utilicé esto recientemente para hacer una implementación rápida de XOR, ya que la función CLR xor opera en ints y necesitaba hacer XOR en una secuencia de bytes.

El código resultante medido es ~ 10 veces más rápido que el equivalente hecho en C # (haciendo XOR en cada byte).

===

No tengo suficiente stackoverflow street credz para editar la pregunta y agregar esto a la lista como # 20, si alguien más pudiera hacerlo, sería genial ;-)

Rosenfield
fuente
3
En lugar de sumergirse en IL, podría haber logrado esto con punteros inseguros. Me imagino que habría sido igual de rápido, y tal vez más rápido, ya que no verificaría los límites.
P Daddy
3

Algo que usan los ofuscadores: puede tener un campo / método / propiedad / evento que todos tengan el mismo nombre.

Jason Haley
fuente
1
Puse una muestra en mi sitio: jasonhaley.com/files/NameTestA.zip En ese zip está el IL y un exe que contiene una clase con la siguiente 'A': el nombre de la clase es A -Event nombre A -Método llamado A -Propiedad llamada A -2 Los campos llamados AI no pueden encontrar una buena referencia para señalarlo, aunque probablemente lo leí en la especificación ecma 335 o en el libro de Serge Lidin.
Jason Haley
2

La herencia de enumeración no es realmente posible:

Puede heredar de una clase Enum. Pero el resultado no se comporta como un Enum en particular. Se comporta ni siquiera como un tipo de valor, sino como una clase ordinaria. Lo del rango es: IsEnum: True, IsValueType: True, IsClass: False

Pero eso no es particularmente útil (a menos que desee confundir a una persona o el tiempo de ejecución en sí).

Zotta
fuente
2

También puede derivar una clase del delegado System.Multicast en IL, pero no puede hacer esto en C #:

// La siguiente definición de clase es ilegal:

public class YourCustomDelegate: MulticastDelegate {}

plaureano
fuente
1

También puede definir métodos de nivel de módulo (también conocido como global) en IL, y C #, por el contrario, solo le permite definir métodos siempre que estén asociados a al menos un tipo.

plaureano
fuente