C#やVB.NETではできないMSILでできることは何ですか?[閉まっている]


165

.NET言語で記述されたすべてのコードはMSILにコンパイルされますが、MSILを直接使用してのみ実行できる特定のタスク/操作はありますか?

MSILでは、C#、VB.NET、F#、j#、その他の.NET言語よりも簡単に処理を実行できます。

これまでのところこれがあります:

  1. テール再帰
  2. ジェネリックCo / Contravariance
  3. 戻り型のみが異なるオーバーロード
  4. アクセス修飾子を上書きする
  5. System.Objectから継承できないクラスがある
  6. フィルタリングされた例外(vb.netで実行できます)
  7. 現在の静的クラス型の仮想メソッドを呼び出します。
  8. 値型のボックス版のハンドルを取得します。
  9. try / faultを実行します。
  10. 禁止された名前の使用。
  11. 値型の独自のパラメーターなしのコンストラクターを定義します
  12. raise要素でイベントを定義します。
  13. CLRでは許可されているが、C#では許可されていない一部の変換。
  14. main()メソッドをとして作成し.entrypointます。
  15. ネイティブとの仕事intとネイティブunsigned intに直接タイプ。
  16. 一時的なポインタで遊ぶ
  17. MethodBodyItemのエミッタバイトディレクティブ
  18. System.Exception以外のタイプをスローしてキャッチする
  19. 列挙型の継承(未検証)
  20. バイトの配列を(4倍小さい)intの配列として扱うことができます。
  21. フィールド/メソッド/プロパティ/イベントにすべて同じ名前を付けることができます(未確認)。
  22. 独自のcatchブロックからtryブロックに戻ることができます。
  23. famandassemアクセス指定子にアクセスできます(protected internalfam または assemです)
  24. <Module>グローバル関数を定義するためのクラス、またはモジュール初期化子への直接アクセス。

17
すばらしい質問です!
Tamas Czinege 2009年

5
F#は末尾再帰をサポートしています:en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink 2009年

4
列挙型を継承しますか?それはとても素敵時々 ..だろう
ジミー・ホッファ

1
Mainメソッドの大文字Mは.NETで
コンクリートガネット2013

4
「非建設的として閉鎖」の主張はばかげている。これは経験的な質問です。
ジムバルター2017年

回答:


34

MSILでは、次の理由により戻り値の型のみが異なるオーバーロードが可能です

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

または

callvirt int32 ...

5
この種のものをどうやって知っていますか?:)
ジェリーシェンク、


8
これは素晴らしいです。リターンオーバーロードを作成したくないのは誰ですか?
ジミーHoffa

12
2つのメソッドが戻り値の型を除いて同一である場合、C#またはvb.netから呼び出すことができますか?
スーパーキャット2012

29

C#やVBを含むほとんどの.Net言語は、MSILコードの末尾再帰機能を使用しません。

テール再帰は、関数型言語で一般的な最適化です。これは、メソッドBの呼び出しが行われるとメソッドAのスタックの割り当てを解除できるように、メソッドBの値を返すことによってメソッドAが終了したときに発生します。

MSILコードはテール再帰を明示的にサポートしており、一部のアルゴリズムでは、これは重要な最適化になる可能性があります。ただし、C#とVBはこれを行うための命令を生成しないため、手動で(またはF#または他の言語を使用して)実行する必要があります。

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

ローカルデータをハードウェアスタックからヒープに割り当てられたスタックデータ構造に移動することにより、再帰を削除するのが一般的です。上記のような末尾呼び出しの再帰除去では、スタックが完全に除去されます。これは非常に優れた最適化です。また、戻り値は長い呼び出しチェーンをたどる必要はありませんが、直接返されます。

ただし、いずれにしても、CILはこの機能を言語の一部として提供しますが、C#またはVBでは手動で実装する必要があります。(ジッターもこの最適化を自由に行うことは自由ですが、それはまったく別の問題です。)


1
F#は、MSILの末尾再帰を使用しません。完全に信頼された(CAS)ケースでのみ機能するためです。これは、アクセス許可の確認などのためにスタックを離れないためです。
リチャード

10
リチャード、どういう意味かわかりません。F#は確かにテールを放出します。プレフィックスを呼び出し、ほとんどすべての場所で。これについてILを調べます:「let print x = print_any x」。
MichaelGG

1
とにかく、JITがテール再帰を使用する場合もあれば、明示的な要求を無視する場合もあります。プロセッサアーキテクチャIIRCによって異なります。
Jon Skeet、

3
@Abel:理論上はプロセッサアーキテクチャは重要ではありませんが、.NETでは、アーキテクチャごとに異なるJITが末尾再帰のルールが異なるため、実際には重要ではありません。言い換えれば、x86では失敗し、x64では失敗しないプログラムが非常に簡単に存在する可能性があります。末尾再帰は、という理由だけですることができます両方のケースに実装され、それが意味するものではありませんです。この質問は特に.NETに関するものであることに注意してください。
Jon Skeet、2010

2
特定の場合、C#は実際にはx64で末尾呼び出しを実行します:community.bartdesmet.net/blogs/bart/archive/2010/07/07/…
Pieter van Ginkel、2011年

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
@ジョン・スキート-敬意を表し、NOAUTOINHERITの意味を教えてください。MSDNは、「基本クラスが指定されていない場合、Objectからのデフォルトの継承を無効にします。.NETFrameworkバージョン2.0の新機能」と規定しています。
Ramesh、

2
@Michael-問題はMSILに関するものであり、Common Intermediate Languageではありません。これはCILでは不可能かもしれないことに同意しますが、それでもMSILで動作します
Ramesh

4
@Ramesh:おっと、あなたは完全に正しいです。その時点では標準仕様に違反しているので使用しないでください。リフレクターはアセンブリをロードしません。ただし、それ ilasmで実行できます。一体なぜそこにあるのだろう。
Jon Skeet、

4
(ああ、コメントの後に/ noautoinheritビットが追加されているのがわかります。少なくとも、以前はそれを実現しなかったほうがいいと思います...)
Jon Skeet

1
少なくともWindows上の.NET 4.5.2ではコンパイルしますが実行されません(TypeLoadException)。PEVerifyは次を返します:[MD]:エラー:インターフェイスではなく、オブジェクトクラスではない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/...
ジョー・シーウェル

18

ああ、その時私はこれに気づかなかった。(あなたがjon-skeetタグを追加する場合、それはより可能性が高いですが、私はそれを頻繁にチェックしません。)

あなたはすでにかなり良い答えを持っているようです。加えて:

  • C#では、値型のボックス版のハンドルを取得できません。あなたはC ++ / CLIでできます
  • C#でtry / faultを実行することはできません(「fault」は「すべてをキャッチしてブロックの最後で再スローする」または「最終的には失敗時のみ」のようなものです)
  • 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
C#でもこれを行うことができます。catch-statementに括弧を付けずにtry/ catchを付けると、Exceptionに似ていない例外もキャッチされます。ただし、スローは実際にから継承した場合にのみ可能ですException
アベル

@Abel参照できないと、何かをキャッチしているとは言えません。
ジムバルター2017年

2
@JimBalterキャッチできない場合、アプリケーションがクラッシュします。キャッチしても、アプリケーションはクラッシュしません。したがって、例外オブジェクトを参照することは、それをキャッチすることとは異なります。
Daniel Earwicker 2017年

笑!アプリの終了と継続の違いはペダントリーですか?今、私はすべてを聞いたことがあると思います。
Daniel Earwicker 2017年

興味深いことに、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!"; }
}

ILと同様に、VBは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:
    // ...

私の知る限り、C#またはVBではこれを行うことはできません


3
これが省略された理由がわかります-はっきりとしたにおいがしますGOTO
基本

3
VB.NETのOn Error Resume Nextに非常に似ています
Thomas Weller

1
これは実際にはVB.NETで実行できます。GoToステートメントはからCatchに分岐できTryます。ここでテストコードをオンライン実行します
mbomb007 2017

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
を削除してください= True、それは私の目を出血させています!
Konrad Rudolph、

どうして?これはC#ではなくVBなので、= / ==の問題はありません。;-)
peSHIr 2009

まあc#は "throw;"を実行できるため、同じ結果を得ることができます。
フランク・シュヴィーターマン

8
paSHIr、彼はその冗長性について話していたと思います
LegendLength 2009年

5
@Frank Schwieterman:例外のキャッチと再スローでは、例外のキャッチを延期するのとは異なります。フィルターはネストされた「finally」ステートメントの前に実行されるため、フィルターの実行時に例外を引き起こした状況が依然として存在します。かなりの数のSocketExceptionがスローされることを期待していて、比較的静かにキャッチしたいが、そのうちのいくつかは問題を通知する場合、問題のあるものがスローされたときの状態を調べることができると非常に便利です。
スーパーキャット、2011


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#は直接アクセスできません。
アベル

一時的なポインタは、非常に便利なタイプのようです。可変構造への反対の多くはそれらの省略から生じます。たとえば、「DictOfPoints(key).X = 5;」のようになります。DictOfPoints(key)が値で構造体をコピーするのではなく、構造体への一時的なポインタを返した場合に機能します。
スーパーキャット

一時的なポインタでは機能しない@supercat、問題のデータはヒープ上にある可能性があります。:何が欲しいのはエリックはおよそここで語っrefのリターンであるblogs.msdn.com/b/ericlippert/archive/2011/06/23/...
ShuggyCoUk

@ShuggyCoUk:興味深い。エリックが「DictOfPoints(key).X = 5;」となる手段を提供するように説得されることを本当に望みます。動作させることができます。現在のところ、DictOfPointsをタイプ(または他の特定のタイプ)と排他的に機能するようにハードコーディングしたい場合は、ほとんど機能しますが、それは面倒です。ところで、私が見たいことの1つは、オープンエンドのジェネリック関数を記述できる手段です。たとえば、DoStuff <...>(someParams、ActionByRef <moreParams、...>、...)必要に応じて拡張できます。MSILでこれを行うにはいくつかの方法があると思います。一部のコンパイラのヘルプを使用して...
supercat

@ShuggyCoUk:...参照プロパティを使用する別の方法を提供します。一部のプロパティは、部外者が参照に対して行うすべての処理が行われた後に実行できるという追加のボーナスがあります。
スーパーキャット2011

6

C#で許可されていないメソッドオーバーライドco / contra-varianceをハックできます(これは一般的な分散とは異なります!)。ここにこれを実装する方法の詳細と、パート12があります。


4

私がずっと望んでいたのは(完全に間違った理由で)Enumでの継承だったと思います。(Enumsは単なるクラスであるため)SMILで行うのは難しいことではないように見えますが、C#構文で求められていることではありません。


4

ここにいくつかあります:

  1. デリゲートに追加のインスタンスメソッドを含めることができます。
  2. デリゲートはインターフェイスを実装できます。
  3. デリゲートとインターフェイスに静的メンバーを含めることができます。

3

20)バイトの配列を整数の(4倍小さい)配列として扱うことができます。

CLRのxor関数はintで動作し、バイトストリームでXORを実行する必要があるため、最近これを使用して高速XOR実装を実行しました。

結果のコードは、C#で実行された同等のコード(各バイトでXORを実行)よりも約10倍高速であると測定されました。

===

質問を編集し、これを#20としてリストに追加するのに十分なstackoverflowストリートcredzがありません。


3
ILに浸るのではなく、安全でないポインタを使用してこれを達成することができます。境界チェックを行わないので、私はそれが同じくらい高速で、おそらく高速だったと思います。
Pダディ

3

難読化ツールが使用するもの-フィールド/メソッド/プロパティ/イベントにすべて同じ名前を付けることができます。


1
私は自分のサイトにサンプルを出しました:jasonhaley.com/files/NameTestA.zip そのzipには、ILと、次のすべてが同じ「A」のクラスを含むexeがあります。-class name is A -Event named Aと名付けられたAメソッド-Aと名付けられたプロパティ-2 AIと名付けられたフィールドは、あなたを指す適切な参照を見つけることができません。
Jason Haley、

2

列挙型の継承は実際には不可能です。

Enumクラスから継承できます。ただし、結果は特にEnumのようには動作しません。値型のようにではなく、通常のクラスのように動作します。奇妙なことは:IsEnum:True、IsValueType:True、IsClass:False

しかし、それは特に便利ではありません(人やランタイム自体を混乱させたくない場合を除きます)。


2

ILでSystem.Multicastデリゲートからクラスを派生させることもできますが、C#ではこれを行うことはできません。

//次のクラス定義は不正です:

パブリッククラスYourCustomDelegate:MulticastDelegate {}


1

ILおよびC#でモジュールレベル(別名グローバル)メソッドを定義することもできます。対照的に、少なくとも1つのタイプにアタッチされているメソッドのみを定義できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.