C # 또는 VB.NET에서 할 수없는 MSIL에서 무엇을 할 수 있습니까? [닫은]


165

.NET 언어로 작성된 모든 코드는 MSIL로 컴파일되지만 MSIL 만 직접 사용할 수있는 특정 작업 / 작업이 있습니까?

MSIL에서 C #, VB.NET, F #, j # 또는 기타 .NET 언어보다 쉽게 ​​수행 할 수있는 작업을 살펴 ​​보겠습니다.

지금까지 우리는 이것을 가지고 있습니다 :

  1. 꼬리 재귀
  2. 일반 공분산
  3. 반환 유형 만 다른 과부하
  4. 액세스 수정 자 무시
  5. System.Object에서 상속 할 수없는 클래스가 있습니다.
  6. 필터링 된 예외 (vb.net에서 수행 가능)
  7. 현재 정적 클래스 유형의 가상 메소드를 호출합니다.
  8. 박스형 버전의 값 유형에 대한 핸들을 가져옵니다.
  9. 시도 / 결함을 수행하십시오.
  10. 금지 된 이름 사용.
  11. 값 유형에 대한 고유 한 매개 변수없는 생성자를 정의하십시오 .
  12. raise요소를 사용하여 이벤트를 정의하십시오 .
  13. CLR에서는 허용하지만 C #에서는 허용하지 않는 일부 변환이 있습니다.
  14. 로 main()방법을 만들지 마십시오 .entrypoint.
  15. 기본 int및 기본 unsigned int유형으로 직접 작업하십시오.
  16. 과도 포인터로 재생
  17. MethodBodyItem의 emitbyte 지시어
  18. 비 System.Exception 유형을 던지고 잡습니다.
  19. 상속 열거 형 (확인되지 ​​않음)
  20. 바이트 배열을 (4 배 더 작은) 정수 배열로 취급 할 수 있습니다.
  21. 필드 / 방법 / 속성 / 이벤트는 모두 같은 이름 (확인되지 ​​않음)을 가질 수 있습니다.
  22. 자체 catch 블록에서 try 블록으로 다시 분기 할 수 있습니다.
  23. famandassem 액세스 지정자에 액세스 할 수 있습니다 ( protected internalfam 또는 assem 임 )
  24. <Module>전역 함수 또는 모듈 이니셜 라이저를 정의 하기 위해 클래스에 직접 액세스합니다 .

17
훌륭한 질문입니다!
— Tamas Czinege

5
F #은 꼬리 재귀를 지원합니다 : en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
— Bas Bossink

4
열거 형을 상속합니까? 때때로 그렇게 좋을 것입니다 ..
— Jimmy Hoffa

1
메인 방법은 .NET에서 자본 M이
— 콘크리트 가넷

4
"비 건설적 폐쇄"주장은 터무니 없다. 이것은 경험적인 질문입니다.
— Jim Balter

답변:


34

MSIL은 다음과 같은 이유로 인해 반환 유형 만 다른 과부하를 허용합니다.

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

또는

callvirt int32 ...

5
이런 종류의 물건을 어떻게 알 수 있습니까? :)
— Gerrie Schenck 's


8
대단해. 리턴 오버로드를 원하지 않는 사람은 누구입니까?
— Jimmy Hoffa

12
반환 유형을 제외하고 두 가지 방법이 동일한 경우 C # 또는 vb.net에서 호출 할 수 있습니까?
— supercat December

29

C # 및 VB를 포함한 대부분의 .Net 언어는 MSIL 코드의 꼬리 재귀 기능을 사용하지 않습니다.

테일 재귀는 기능적 언어에서 일반적인 최적화입니다. 메소드 B가 호출되면 메소드 A의 스택이 할당 해제 될 수 있도록 메소드 B의 값을 리턴하여 메소드 A가 종료 될 때 발생합니다.

MSIL 코드는 꼬리 재귀를 명시 적으로 지원하며 일부 알고리즘의 경우 중요한 최적화가 될 수 있습니다. 그러나 C # 및 VB는이를 수행하기위한 지시 사항을 생성하지 않으므로 수동으로 (또는 F # 또는 다른 언어를 사용하여) 수행해야합니다.

C #에서 tail-recursion을 수동으로 구현하는 방법의 예는 다음과 같습니다.

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

하드웨어 스택에서 힙 할당 스택 데이터 구조로 로컬 데이터를 이동하여 재귀를 제거하는 것이 일반적입니다. 위와 같이 tail-call 재귀 제거에서 스택이 완전히 제거됩니다. 이는 상당히 좋은 최적화입니다. 또한 반환 값은 긴 콜 체인을 걸을 필요는 없지만 직접 반환됩니다.

그러나 CIL은이 기능을 언어의 일부로 제공하지만 C # 또는 VB에서는 수동으로 구현해야합니다. (지터는 또한이 최적화를 자체적으로 자유롭게 만들 수 있지만, 그것은 완전히 다른 문제입니다.)


1
F #은 MSIL의 꼬리 재귀를 사용하지 않습니다. 왜냐하면 MSA (권한 주장 등)를 확인하기 위해 스택을 떠나지 않는 방식으로 완전 신뢰 (CAS) 사례에서만 작동하기 때문입니다.
— Richard

10
리차드, 무슨 말인지 잘 모르겠습니다. F #은 확실히 꼬리를 내 보냅니다. 거의 모든 곳에서 전화 접두사. IL에 대해 "let print x = print_any x"를 검사하십시오.
— MichaelGG

1
나는 JIT가 어쨌든 꼬리 재귀를 사용할 것이라고 믿습니다. 어떤 경우에는 명시 적 요청을 무시할 것입니다. 프로세서 아키텍처 IIRC에 따라 다릅니다.
— Jon Skeet

3
@Abel : 프로세서 아키텍처는 이론상 관련이 없지만 관련이 없습니다. 실제로 는 아키텍처마다 다른 JIT가 .NET에서 꼬리 재귀에 대한 규칙이 다르기 . 다시 말해, x86에서는 그렇지 않지만 x64에서는 그렇지 않은 프로그램을 매우 쉽게 가질 수 있습니다. 꼬리 재귀가해서 할 수 두 경우 모두에서 구현 될 그것을 의미하지 않는다 이다 . 이 질문은 특히 .NET에 관한 것입니다.
— Jon Skeet

2
: C #을 실제로 특정의 경우에 64에서 꼬리 호출하지 community.bartdesmet.net/blogs/bart/archive/2010/07/07/...을 .
— Pieter van Ginkel

21

MSIL에는 System.Object에서 상속 할 수없는 클래스가있을 수 있습니다.

샘플 코드 : ilasm.exe로 컴파일 업데이트 : "/ NOAUTOINHERIT"를 사용하여 어셈블러가 자동 상속되지 않도록해야합니다.

// 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-모든 것을 존중하여 NOAUTOINHERIT의 의미를 이해하도록 도와주십시오. MSDN은 "기본 클래스가 지정되지 않은 경우 Object에서 기본 상속을 비활성화합니다. .NET Framework 버전 2.0의 새로운 기능"
— Ramesh

2
@Michael-이 질문은 MSIL과 관련이 있으며 일반적인 중급 언어는 아닙니다. CIL에서는 불가능할 수도 있지만 MSIL 과도 작동합니다.
— Ramesh

4
@Ramesh : 죄송합니다. 그렇습니다. 그 시점에서 표준 사양을 위반하므로 사용해서는 안됩니다. 리플렉터는 어셈블러를 적재하지도 않습니다. 그러나, 그것은 일라 즘으로 이루어질 수 있습니다. 나는 왜 지구상에 있는지 궁금합니다.
— Jon Skeet

4
(아, 나는 / noautoinherit 비트가 내 댓글 이후에 추가 된 참조 적어도 나는 약간 기분에 대해 전에 그것을 실현하지 ....)
— 존 소총

1
적어도 Windows의 .NET 4.5.2에서는 컴파일하지만 실행하지는 않습니다 ( TypeLoadException). PEVerify는 다음을 반환합니다. [MD] : 오류 : 인터페이스가 아니고 Object 클래스가 아닌 TypeDef는 Nil 토큰을 확장합니다.
— xanatos

20

protected및 internal수정자를 결합 할 수 있습니다. C #에서 protected internal멤버 를 작성 하면 어셈블리 및 파생 클래스에서 액세스 할 수 있습니다. 비아 MSIL 당신은 어셈블리에서 파생 된 클래스에서 액세스 할 수있는 멤버 얻을 수 있습니다 만 . (나는 그것이 매우 유용 할 수 있다고 생각합니다!)


4
현재 C # 7.1 ( github.com/dotnet/csharplang/issues/37 ) 에서 구현 될 후보 이며 액세스 수정자는private protected
— Happypig375

5
C # 7.2의 일부로 출시되었습니다 : blogs.msdn.microsoft.com/dotnet/2017/11/15/…
— Joe Sewell

18

오, 나는 당시에 이것을 발견하지 못했습니다. jon-skeet 태그를 추가하면 더 가능성이 높지만 자주 확인하지는 않습니다.

이미 좋은 답변을 얻은 것 같습니다. 게다가:

  • C #에서는 박스형 버전의 값 형식을 처리 할 수 ​​없습니다. C ++ / CLI에서 가능
  • C #에서는 시도 / 결함을 수행 할 수 없습니다 ( "결함"은 "모든 것을 잡아서 블록 끝에서 다시 던지다"또는 "마지막이지만 실패한 경우에만"과 같습니다)
  • C #에서는 금지하지만 법적 IL에는 많은 이름이 있습니다.
  • IL을 사용하면 값 형식에 대한 고유 한 매개 변수없는 생성자 를 정의 할 수 있습니다 .
  • C #에서 "raise"요소를 사용하여 이벤트를 정의 할 수 없습니다. (VB에서는 이 사용자 정의 이벤트에 있지만 "기본"이벤트 하나를 포함하지 않습니다.)
  • 일부 변환은 CLR에서는 허용되지만 C #에서는 허용되지 않습니다. 당신을 통해 갈 경우 objectC #에서 이러한 작업 가끔 것이다. 예를 들어 uint [] / int [] SO 질문 을 참조하십시오 .

다른 것을 생각하면 여기에 추가하겠습니다 ...


3
아, 존스 키트 태그, 내가 뭔가 빠졌음을 알았습니다!
— Binoj Antony

잘못된 식별자 이름을 사용하려면 C #에서 @ 접두사를 붙일 수 있습니다.
— George Polevoy

4
@George : 키워드에는 적용되지만 모든 유효한 IL 이름은 아닙니다. <>aC #에서 이름으로 지정해보십시오 .
— Jon Skeet


14

IL에서는에서 파생 된 형식뿐만 아니라 모든 형식을 던지고 잡을 수 있습니다 System.Exception.


6
catch 문에 괄호를 사용 try하거나 사용 catch하지 않고 C #에서도 그렇게 할 수 있습니다 . 예외가 아닌 예외도 잡을 수 있습니다. 그러나 던지는 것은에서 상속받을 때만 가능합니다 Exception.
— Abel

@Abel 당신이 그것을 참조 할 수 없다면 무언가를 잡고 있다고 말할 수 없습니다.
— Jim Balter

2
@JimBalter 잡지 않으면 응용 프로그램이 중단됩니다. 잡으면 응용 프로그램이 충돌하지 않습니다. 따라서 예외 객체를 언급하는 것은 그것을 잡는 것과 다릅니다.
— Daniel Earwicker

롤! 앱 종료 또는 계속의 차이점은 pedantry입니까? 이제 나는 모든 것을들을 수 있다고 생각합니다.
— Daniel Earwicker

흥미롭게도 CLR에서는 더 이상이 작업을 수행 할 수 없습니다. 기본적으로 RuntimeWrappedException 에서 예외가 아닌 객체를 래핑 합니다.
— Jwosty

10

IL은 가상 메소드 호출 call과 callvirt가상 메소드 호출을 구분 합니다. 전자를 사용하면 동적 클래스 유형의 가상 함수 대신 현재 정적 클래스 유형 의 가상 메소드를 강제로 호출 할 수 있습니다 .

C #은이 작업을 수행 할 방법이 없습니다.

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는 IL과 같이 MyClass.Method()구문 을 사용하여 비가 상 호출을 발행 할 수 있습니다 . 위의 내용은입니다 MyClass.ToString().


9

try / catch에서는 자체 catch 블록에서 try 블록을 다시 입력 할 수 있습니다. 그래서 당신은 이것을 할 수 있습니다 :

.try {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

AFAIK C # 또는 VB에서는이 작업을 수행 할 수 없습니다


3
왜 이것이 생략되었는지 알 수 있습니다.-독특한 냄새가 있습니다GOTO
— Basic

3
VB.NET에서 On Error Resume Next 와 비슷한 소리
— Thomas Weller

1
실제로 VB.NET에서 수행 할 수 있습니다. GoTo 문은에서 Catch로 분기 될 수 있습니다Try . 여기에서 온라인으로 테스트 코드를 실행 하십시오 .
— mbomb007

9

IL 및 VB.NET을 사용하면 예외를 포착 할 때 필터를 추가 할 수 있지만 C # v3은이 기능을 지원하지 않습니다.

이 VB.NET 예제가에서 가져 http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx (음 When ShouldCatch(ex) = True의를 캐치 조항) :

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

17
r를 제거하면 = True눈이 번집니다!
— Konrad Rudolph

왜? 이것은 VB이며 C #이 아니므로 = / == 문제가 없습니다. ;-)
— peSHIr

c #은 "throw;"를 수행 할 수 있으므로 동일한 결과를 얻을 수 있습니다.
— Frank Schwieterman 2016 년

8
paSHIr, 나는 그가 그것의
— 중복성

5
@Frank Schwieterman : 예외를 잡는 것과 다시 던지는 것과 그것을 잡는 것을 보류하는 것에는 차이가 있습니다. 필터는 중첩 된 "최종"문보다 먼저 실행되므로 필터를 실행할 때 예외를 발생시킨 상황이 여전히 존재합니다. 상당한 수의 SocketException이 발생한다고 예상하는 경우 비교적 조용히 잡기를 원하지만 그 중 일부는 문제를 표시하므로 문제가 발생한 상황을 검사 할 수 있으면 매우 유용 할 수 있습니다.
— supercat


7

Native types
네이티브 int 및 네이티브 unsigned int 유형으로 직접 작업 할 수 있습니다 (c #에서는 동일하지 않은 IntPtr에서만 작업 할 수 있습니다).

Transient Pointers
관리되는 유형에 대한 포인터 인 임시 포인터를 사용하여 재생할 수 있지만 관리되는 힙에 없기 때문에 메모리에서 이동하지 않아야합니다. 관리되지 않는 코드를 엉망으로 만들지 않고 어떻게 이것을 유용하게 사용할 수 있는지는 확실하지 않지만 stackalloc과 같은 것들을 통해서만 다른 언어에 직접 노출되지는 않습니다.

<Module>
원한다면 수업에 엉망이 될 수 있습니다 (IL이 필요하지 않은 채 반영 하여이 작업을 수행 할 수 있습니다)

.emitbyte

15.4.1.1 .emitbyte 지시어 MethodBodyItem :: =… | .emitbyte Int32이 지시문은 부호가없는 8 비트 값이 지시문이 나타나는 지점에서 메소드의 CIL 스트림으로 직접 방출되도록합니다. [참고 : .emitbyte 지시문은 테스트 생성에 사용됩니다. 정기적 인 프로그램 생성에는 필요하지 않습니다. 끝 참고]

.entrypoint
이것에 대해 좀 더 융통성이 있습니다. 예를 들어 Main이라고 불리는 메소드에 적용 할 수 있습니다.

스펙을 읽으 십시오. 몇 가지 더 찾을 것입니다.


+1, 여기 숨겨진 보석. 주 <Module>(VB가하는 것처럼) 글로벌 방법을 허용하지만 실제로 C #을 직접 액세스 할 수 없습니다 언어에 대한 특별 클래스로서 의미한다.
— Abel

일시적인 포인터는 매우 유용한 유형 인 것 같습니다. 가변 구조에 대한 많은 반대 의견은 누락에 기인합니다. 예를 들어 "DictOfPoints (key) .X = 5;" DictOfPoints (key)가 구조체를 값으로 복사하지 않고 구조체에 대한 임시 포인터를 반환하면 작동 가능합니다.
— supercat 2016 년

일시적 포인터로 작동하지 않는 @supercat은 문제의 데이터가 힙에있을 수 있습니다. 당신이 원하는 것은 심판이 여기에 대한 Eric의 이야기를 반환하는 것입니다 : blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
— ShuggyCoUk

@ShuggyCoUk : 흥미 롭습니다. 에릭이 "DictOfPoints (key) .X = 5;" 작동하도록 만들 수 있습니다. 현재 DictOfPoints를 Point 유형 (또는 다른 특정 유형)과 독점적으로 작동하도록 하드 코딩하려는 경우에는 거의 해당 작업을 수행 할 수 있지만 고통 스럽습니다. BTW, 내가보고 싶은 한 가지는 DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...)와 같은 개방형 일반 함수를 작성할 수있는 수단입니다. 필요에 따라 확장 할 수 있습니다. MSIL에는 그렇게 할 수있는 방법이있을 것 같습니다. 컴파일러의 도움으로 ...
— supercat

@ShuggyCoUk : ... 외부 참조가 수행 한 모든 작업이 완료된 후 일부 속성이 실행될 수있는 추가 보너스와 함께 참조 기준 속성을 갖는 또 다른 방법을 제공합니다.
— supercat

6

C #에서 허용하지 않는 메서드 재정의 공 / 대비 분산을 해킹 할 수 있습니다 (일반 분산과 동일하지 않습니다!). 나는이 구현에 대한 자세한 정보를 가지고 여기 , 및 부품 (1) 과 (2)


4

나는 내가 계속 원했던 것은 (전적으로 잘못된 이유로) Enums의 상속이라고 생각합니다. SMIL에서하는 것이 어렵지 않은 것처럼 보이지만 (Enum은 클래스이므로) C # 구문에서 원하는 것이 아닙니다.


4

몇 가지 더 있습니다 :

  1. 델리게이트에 추가 인스턴스 메소드를 가질 수 있습니다.
  2. 대리인은 인터페이스를 구현할 수 있습니다.
  3. 델리게이트 및 인터페이스에 정적 멤버를 가질 수 있습니다.

3

20) 바이트 배열을 (4 배 더 작은) 정수 배열로 취급 할 수 있습니다.

CLR xor 함수는 int에서 작동하고 바이트 스트림에서 XOR을 수행해야했기 때문에 최근에 빠른 XOR 구현을 위해 이것을 사용했습니다.

결과 코드는 C # (각 바이트에서 XOR 수행)에서 수행하는 것보다 ~ 10 배 빠릅니다.

===

다른 사람이 부풀어 올 수 있다면 질문을 편집하고 # 20으로 목록에 추가 할 수있는 충분한 스택 오버 스트리트 거리 개가 없습니다. ;-)


3
IL에 빠지는 대신 안전하지 않은 포인터로이를 달성 할 수 있습니다. 나는 그것이 경계 검사를하지 않기 때문에 그것이 빠르거나 빠를 것이라고 상상할 것입니다.
— P Daddy

3

난 독자가 사용하는 것-필드 / 방법 / 속성 / 이벤트는 모두 같은 이름을 가질 수 있습니다.


1
내 사이트에 샘플을 넣었습니다. jasonhaley.com/files/NameTestA.zip 해당 zip에는 IL과 exe가 있으며 모두 같은 'A'를 가진 클래스를 포함합니다. -class name is A -Event named AI라는 이름의 방법 -2 AI라는 이름의 필드는 ecma 335 사양이나 Serge Lidin의 책에서 읽을 수는 있지만 참고할만한 참고 자료는 찾을 수 없습니다.
— Jason Haley

2

열거 형 상속은 실제로 불가능합니다.

Enum 클래스에서 상속 할 수 있습니다. 그러나 결과는 특히 Enum처럼 행동하지 않습니다. 그것은 가치 유형처럼 행동하는 것이 아니라 일반 클래스처럼 행동합니다. 이상한 것은 IsEnum : True, IsValueType : True, IsClass : False입니다.

그러나 사람이나 런타임 자체를 혼동하지 않는 한 특별히 유용하지는 않습니다.


2

IL의 System.Multicast 델리게이트에서 클래스를 파생시킬 수도 있지만 C #에서는이 작업을 수행 할 수 없습니다.

// 다음 클래스 정의는 유효하지 않습니다.

공개 클래스 YourCustomDelegate : 멀티 캐스트 Delegate {}


1

IL에서 모듈 수준 (일명 글로벌) 메서드를 정의 할 수도 있고 C #과는 달리 적어도 하나의 유형에 연결된 경우에만 메서드를 정의 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.