O que você pode fazer no MSIL e não no C # ou no VB.NET? [fechadas]

165

Todo o código escrito nas linguagens .NET é compilado para o MSIL, mas existem tarefas / operações específicas que você só pode fazer usando o MSIL diretamente?

Vamos também fazer coisas mais fáceis no MSIL do que C #, VB.NET, F #, j # ou qualquer outra linguagem .NET.

Até agora, temos o seguinte:

  1. Recursão da cauda
  2. Co / Contravariância Genérica
  3. Sobrecargas que diferem apenas nos tipos de retorno
  4. Substituir modificadores de acesso
  5. Tem uma classe que não pode herdar do System.Object
  6. Exceções filtradas (podem ser feitas em vb.net)
  7. Chamando um método virtual do tipo de classe estática atual.
  8. Identifique a versão em caixa de um tipo de valor.
  9. Faça uma tentativa / falha.
  10. Uso de nomes proibidos.
  11. Defina seus próprios construtores sem parâmetros para tipos de valor .
  12. Defina eventos com um raiseelemento.
  13. Algumas conversões permitidas pelo CLR, mas não pelo C #.
  14. Faça um main()método não como o .entrypoint.
  15. trabalhe diretamente com os tipos intnativo e nativo unsigned int.
  16. Brincar com ponteiros transitórios
  17. diretiva emitbyte em MethodBodyItem
  18. Lançar e capturar tipos não System.Exception
  19. Herdar enums (não verificado)
  20. Você pode tratar uma matriz de bytes como uma matriz de ints (4x menor).
  21. Você pode ter um campo / método / propriedade / evento com o mesmo nome (Não verificado).
  22. Você pode ramificar novamente em um bloco try a partir de seu próprio bloco catch.
  23. Você tem acesso ao especificador de acesso famandassem ( protected internalé fam ou assem)
  24. Acesso direto à <Module>classe para definir funções globais ou um inicializador de módulo.
Binoj Antony
fonte
17
Excelente pergunta!
Tamas Czinege 13/03/09
5
F # faz recursão de cauda apoio ver: en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink
4
Herdar enums? Isso seria tão bom, por vezes ..
Jimmy Hoffa
1
O método principal tem uma M de capital no .NET
Concrete Gannet
4
A afirmação "fechada como não construtiva" é absurda. Esta é uma pergunta empírica.
Jim Balter 02/01

Respostas:

34

O MSIL permite sobrecargas que diferem apenas nos tipos de retorno devido a

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

ou

callvirt int32 ...
Anton Gogolev
fonte
5
Como você conhece esse tipo de coisa? :)
Gerrie Schenck
8
Isso é incrível. Quem não quis fazer sobrecargas de retorno?
Jimmy Hoffa
12
Se dois métodos são idênticos, exceto para o tipo de retorno, podem ser chamados em C # ou vb.net?
Super12 dez12
29

A maioria dos idiomas .Net, incluindo C # e VB, não usa o recurso de recursão de cauda do código MSIL.

A recursão da cauda é uma otimização comum em linguagens funcionais. Ocorre quando um método A termina retornando o valor do método B, para que a pilha do método A possa ser desalocada depois que a chamada para o método B é feita.

O código MSIL suporta explicitamente a recursão da cauda e, para alguns algoritmos, isso pode ser uma otimização importante a ser feita. Mas como C # e VB não geram as instruções para fazer isso, isso deve ser feito manualmente (ou usando F # ou algum outro idioma).

Aqui está um exemplo de como a recursão de cauda pode ser implementada manualmente em 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;
    }
}

É prática comum remover a recursão movendo os dados locais da pilha de hardware para uma estrutura de dados da pilha alocada em heap. Na eliminação da recursão de chamada final, como mostrado acima, a pilha é eliminada completamente, o que é uma otimização bastante boa. Além disso, o valor de retorno não precisa subir uma longa cadeia de chamadas, mas é retornado diretamente.

Mas, de qualquer maneira, o CIL fornece esse recurso como parte do idioma, mas com C # ou VB ele precisa ser implementado manualmente. (O tremor também é livre para fazer essa otimização por conta própria, mas isso é outra questão.)

Jeffrey L Whitledge
fonte
1
O F # não usa a recursão final do MSIL, porque funciona apenas em casos totalmente confiáveis ​​(CAS) devido ao modo como não deixa uma pilha para verificar as asserções de permissão (etc.).
Richard Richard
10
Richard, não sei o que você quer dizer. F # certamente emite a cauda. prefixo de chamada, praticamente em todo o lugar. Examine o IL para isso: "deixe imprimir x = imprimir_qualquer x".
22420 MichaelGG
1
Acredito que o JIT usará a recursão final de qualquer maneira em alguns casos - e em alguns casos ignorará a solicitação explícita. Depende da arquitetura do processador, IIRC.
26909 Jon Skeet
3
@Abel: Embora a arquitetura do processador seja irrelevante em teoria , na prática não é irrelevante , pois os diferentes JITs para diferentes arquiteturas têm regras diferentes para recursão final no .NET. Em outras palavras, você poderia facilmente ter um programa que explodisse em x86, mas não em x64. Só porque a recursão da cauda pode ser implementada nos dois casos, não significa que seja . Observe que esta pergunta é sobre o .NET especificamente.
31910 Jon Skeet
2
O C # realmente reduz chamadas em x64 em casos específicos: community.bartdesmet.net/blogs/bart/archive/2010/07/07/… .
Pieter van Ginkel
21

No MSIL, você pode ter uma classe que não pode herdar do System.Object.

Código de exemplo: compile-o com ilasm.exe ATUALIZAÇÃO: Você deve usar "/ NOAUTOINHERIT" para impedir a montagem automática da herança.

// 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
fonte
2
@ Jon Skeet - Com todo o respeito, você poderia me ajudar a entender o que significa NOAUTOINHERIT. O MSDN especifica "Desativa a herança padrão do Object quando nenhuma classe base é especificada. Novo no .NET Framework versão 2.0".
22640 Ramesh
2
@ Michael - A questão é sobre o MSIL e não o idioma intermediário comum. Eu concordo que isso pode não ser possível em CIL mas, ainda trabalha com MSIL
Ramesh
4
@Ramesh: Opa, você está absolutamente certo. Eu diria que nesse momento ele quebra as especificações padrão e não deve ser usado. O refletor nem carrega o conjunto. No entanto, isso pode ser feito com ilasmo. Eu me pergunto por que diabos está lá.
23909 Jon Skeet
4
(Ah, eu vejo o bit / noautoinherit foi adicionado depois do meu comentário Pelo menos eu me sinto um pouco melhor sobre não perceber isso antes ....)
Jon Skeet
1
Vou acrescentar que pelo menos no .NET 4.5.2 no Windows ele compila, mas não executa ( TypeLoadException). PEVerify retorna: [MD]: Erro: TypeDef que não é uma Interface e não a classe Object estende o token Nil.
Xanatos
20

É possível combinar os modificadores protectede internalacesso. Em C #, se você escrever, protected internalum membro poderá ser acessado a partir do assembly e de classes derivadas. Via MSIL você pode obter um membro que é acessível a partir de classes derivadas dentro da montagem única . (Eu acho que poderia ser bastante útil!)

yatima2975
fonte
4
Agora é um candidato a ser implementado no C # 7.1 ( github.com/dotnet/csharplang/issues/37 ), o modificador de acesso éprivate protected
#
5
Foi lançado como parte do C # 7.2: blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Joe Sewell
18

Ooh, eu não vi isso na época. (Se você adicionar a tag jon-skeet, é mais provável, mas eu não a checo com tanta frequência.)

Parece que você já tem respostas muito boas. Além do que, além do mais:

  • Você não pode controlar a versão em caixa de um tipo de valor em C #. Você pode em C ++ / CLI
  • Você não pode fazer uma tentativa / falha em C # ("falha" é como um "pegue tudo e repita no final do bloco" ou "finalmente, mas apenas com falha")
  • Existem muitos nomes proibidos por C #, mas a IL legal
  • O IL permite definir seus próprios construtores sem parâmetros para tipos de valor .
  • Você não pode definir eventos com um elemento "raise" em C #. (No VB você precisa para eventos personalizados, mas os eventos "padrão" não incluem um.)
  • Algumas conversões são permitidas pelo CLR, mas não pelo C #. Se você usar o objectC #, isso às vezes funcionará. Veja uma pergunta SO uint [] / int [] SO para obter um exemplo.

Vou acrescentar a isso se pensar em mais alguma coisa ...

Jon Skeet
fonte
3
Ah, a tag jon-skeet, eu sabia que estava faltando algo!
26610 Binoj Antony
Para usar o nome do identificador ilegal pode prefixo com @ in C #
George Polevoy
4
@ George: Isso funciona para palavras-chave, mas nem todos os nomes IL válidos. Tente especificar <>acomo um nome em C # ...
Jon Skeet
17

O CLR já suporta co / contravariância genérica, mas o C # não está recebendo esse recurso até 4.0

ermau
fonte
Você pode fornecer um link com mais informações sobre isso?
22711 Binoj Antony
14

Em IL, você pode lançar e capturar qualquer tipo, não apenas os tipos derivados System.Exception.

Daniel Earwicker
fonte
6
Você também pode fazer isso em C #, com try/ catchsem parênteses na instrução catch. Também capturará exceções que não são do tipo exceção. Jogar, no entanto, só é possível quando você herda Exception.
Abel
@ Abel Você dificilmente pode dizer que está pegando alguma coisa se não pode se referir a ela.
Jim Balter
2
@ JimBalter, se você não o pegar, o aplicativo trava. Se você pegá-lo, o aplicativo não trava. Portanto, referir-se ao objeto de exceção é diferente de capturá-lo.
Daniel Earwicker
Ri muito! Uma distinção entre o encerramento ou a continuação de um aplicativo é pedantismo? Agora acho que já ouvi tudo.
Daniel Earwicker
Curiosamente, o CLR não permite mais fazer isso. Por padrão, ele agrupará objetos de não exceção em RuntimeWrappedException .
Jwosty
10

IL tem a distinção entre calle callvirtpara chamadas de método virtual. Usando o primeiro, você pode forçar a chamada de um método virtual do tipo de classe estática atual em vez da função virtual no tipo de classe dinâmica.

C # não tem como fazer isso:

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

O VB, como o IL, pode emitir chamadas não virtuais usando a MyClass.Method()sintaxe. No exposto, isso seria MyClass.ToString().

Konrad Rudolph
fonte
9

Em uma tentativa / captura, você pode inserir novamente o bloco try a partir do seu próprio bloco catch. Então, você pode fazer isso:

.try {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

AFAIK, você não pode fazer isso em C # ou VB

thecoop
fonte
3
Eu posso ver por que isso foi omitido - Tem um cheiro distinto deGOTO
Básico
3
Sons muito como On Error Resume seguida em VB.NET
Thomas Weller
1
Na verdade, isso pode ser feito no VB.NET. A instrução GoTo pode ramificar Catchpara aTry . Execute o código de teste online aqui .
Mbomb007
9

Com o IL e o VB.NET, você pode adicionar filtros ao capturar exceções, mas o C # v3 não suporta esse recurso.

Este exemplo do VB.NET é obtido em http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx (observe a When ShouldCatch(ex) = Trueseção Cláusula de captura):

Try
   Foo()
Catch ex As CustomBaseException When ShouldCatch(ex)
   Console.WriteLine("Caught exception!")
End Try
Emanuele Aina
fonte
17
Por favor, remova o = True, está fazendo meus olhos sangrarem!
239 Konrad Rudolph
Por quê? Isso é VB e não C #, então não existem problemas = / ==. ;-)
peSHIr 27/02/09
Bem, o c # pode executar "throw"; portanto, o mesmo resultado pode ser alcançado.
24330 Frank Schwieterman
8
paSHIr, eu acredito que ele estava falando sobre o redudancy dele
LegendLength
5
@Frank Schwieterman: Há uma diferença entre capturar e refazer uma exceção, em vez de adiá-la. Os filtros são executados antes de qualquer instrução "finalmente" aninhada, portanto as circunstâncias que causaram a exceção ainda existirão quando o filtro for executado. Se alguém espera que um número significativo de SocketException seja acionado, ele desejará capturar relativamente silenciosamente, mas alguns deles sinalizarão problemas, podendo examinar o estado em que um problema é lançado pode ser muito útil.
Supercat
7

Native types
Você pode trabalhar diretamente com os tipos int nativo e int não assinado nativo (em c #, você só pode trabalhar em um IntPtr que não seja o mesmo.

Transient Pointers
Você pode jogar com ponteiros transitórios, que são ponteiros para tipos gerenciados, mas garantidos para não serem movidos na memória, pois não estão no heap gerenciado. Não tem muita certeza de como você poderia usá-lo de maneira útil sem mexer com código não gerenciado, mas ele não é exposto a outros idiomas diretamente apenas através de coisas como stackalloc.

<Module>
você pode mexer com a classe se assim o desejar (você pode fazer isso refletindo sem precisar de IL)

.emitbyte

15.4.1.1 A diretiva .emitbyte MethodBodyItem :: =… | .emitbyte Int32 Essa diretiva faz com que um valor de 8 bits não assinado seja emitido diretamente no fluxo CIL do método, no ponto em que a diretiva aparece. [Nota: A diretiva .emitbyte é usada para gerar testes. Não é necessário na geração de programas regulares. nota final]

.entrypoint
Você tem um pouco mais de flexibilidade nisso, pode aplicá-lo a métodos não chamados Main, por exemplo.

leia as especificações . Tenho certeza que você encontrará mais algumas.

ShuggyCoUk
fonte
+1, algumas jóias escondidas aqui. Observe que isso <Module>significa uma classe especial para idiomas que aceitam métodos globais (como o VB), mas, na verdade, o C # não pode acessá-lo diretamente.
Abel
Ponteiros transitórios parecem ser um tipo muito útil; muitas das objeções a estruturas mutáveis ​​decorrem de sua omissão. Por exemplo, "DictOfPoints (key) .X = 5;" seria viável se DictOfPoints (key) retornasse um ponteiro transitório para uma estrutura, em vez de copiar a estrutura por valor.
supercat
@ supercat que não funcionaria com um ponteiro transitório, os dados em questão poderiam estar na pilha. o que você quer é que o juiz retorne o que Eric fala aqui: blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
ShuggyCoUk
@ShuggyCoUk: Interessante. Eu realmente espero que Eric possa ser persuadido a fornecer um meio pelo qual "DictOfPoints (key) .X = 5;" poderia ser feito para funcionar. No momento, se alguém deseja codificar DictOfPoints para trabalhar exclusivamente com o tipo Point (ou algum outro tipo específico), pode-se quase fazer isso funcionar, mas é uma dor. BTW, uma coisa que eu gostaria de ver seria um meio pelo qual alguém pudesse escrever uma função genérica aberta, por exemplo, DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...) que pode expandir conforme necessário. Eu acho que haveria alguma maneira de fazer isso no MSIL; com alguma ajuda do compilador ...
supercat
@ShuggyCoUk: ... forneceria outra maneira de ter propriedades por referência, com o bônus adicional de que alguma propriedade poderia ser executada depois que tudo o que os forasteiros fizerem com a referência tiver sido feito.
Supercat
6

Você pode hackear a substituição de método co / contra-variação, que o C # não permite (NÃO é o mesmo que variação genérica!). Eu tenho mais informações sobre como implementar isso aqui , e as partes 1 e 2

thecoop
fonte
4

Eu acho que o que eu continuava desejando (com razões inteiramente erradas) era herança na Enums. Não parece uma coisa difícil de fazer no SMIL (já que Enums são apenas classes), mas não é algo que a sintaxe do C # queira que você faça.

Shalom Craimer
fonte
4

Aqui está um pouco mais:

  1. Você pode ter métodos de instância extras em delegados.
  2. Delegados podem implementar interfaces.
  3. Você pode ter membros estáticos em delegados e interfaces.
leppie
fonte
3

20) Você pode tratar uma matriz de bytes como uma matriz (4x menor) de entradas.

Eu usei isso recentemente para fazer uma implementação rápida do XOR, já que a função CLR xor opera em ints e eu precisava fazer o XOR em um fluxo de bytes.

O código resultante medido para ser ~ 10x mais rápido que o equivalente feito em C # (executando XOR em cada byte).

===

Eu não tenho credibilidade de rua de stackoverflow suficiente para editar a pergunta e adicioná-la à lista como # 20, se alguém puder, isso seria inchado ;-)

Rosenfield
fonte
3
Em vez de mergulhar na IL, você poderia ter conseguido isso com indicadores inseguros. Eu imagino que teria sido tão rápido, e talvez mais rápido, uma vez que não faria verificação de limites.
P Daddy
3

Algo que os ofuscadores usam - você pode ter um campo / método / propriedade / evento, todos com o mesmo nome.

Jason Haley
fonte
1
Coloquei uma amostra no meu site: jasonhaley.com/files/NameTestA.zip Nesse zip, há o IL e um exe que contém uma classe com o seguinte 'A': -class name is A -Event named A -Method chamado A -Property chamado A -2 Os campos chamados AI não conseguem encontrar uma boa referência para apontá-lo, embora eu provavelmente o tenha lido nas especificações do ecma 335 ou no livro de Serge Lidin.
21139 Jason Haley
2

A herança de enum não é realmente possível:

Você pode herdar de uma classe Enum. Mas o resultado não se comporta como um Enum em particular. Ele se comporta nem mesmo como um tipo de valor, mas como uma classe comum. A coisa mais interessante é: IsEnum: True, IsValueType: True, IsClass: False

Mas isso não é particularmente útil (a menos que você queira confundir uma pessoa ou o próprio tempo de execução.)

Zotta
fonte
2

Você também pode derivar uma classe do delegado System.Multicast em IL, mas não pode fazer isso em C #:

// A seguinte definição de classe é ilegal:

classe pública YourCustomDelegate: MulticastDelegate {}

plaureano
fonte
1

Você também pode definir métodos em nível de módulo (também conhecidos como globais) em IL, e o C #, por outro lado, permite definir métodos apenas desde que estejam anexados a pelo menos um tipo.

plaureano
fonte