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

protectedinternal수정자를 결합 할 수 있습니다. 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은 가상 메소드 호출 callcallvirt가상 메소드 호출을 구분 합니다. 전자를 사용하면 동적 클래스 유형의 가상 함수 대신 현재 정적 클래스 유형 의 가상 메소드를 강제로 호출 할 수 있습니다 .

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.