Что вы можете сделать в MSIL, что вы не можете сделать на С# или VB.NET?

Весь код, написанный на языках .NET, компилируется в MSIL, но существуют ли определенные задачи/операции, которые вы можете использовать только с помощью MSIL напрямую?

Давайте также облегчим работу в MSIL, чем С#, VB.NET, F #, j # или любой другой язык .NET.

До сих пор мы имеем это:

  • Рекурсия хвоста
  • Общие Co/Контравариантность
  • Перегрузки, которые отличаются только типами возврата
  • Модификаторы переопределения доступа
  • Имейте класс, который не может наследовать из System.Object
  • Отфильтрованные исключения (можно сделать в vb.net)
  • Вызов виртуального метода текущего типа статического класса.
  • Получить дескриптор в коробке версии типа значения.
  • Сделайте попытку/ошибку.
  • Использование запрещенных имен.
  • Определите собственные конструкторы без параметров для типов значений.
  • Определить события с помощью элемента raise.
  • Некоторые преобразования, разрешенные CLR, но не С#.
  • Создайте метод main() как .entrypoint.
  • напрямую работать с родными int и родными unsigned int.
  • Игра с переходными указателями
  • директива emitbyte в MethodBodyItem
  • Бросить и уловить не System.Exception типы
  • Inherit Enums (Не проверено)
  • Вы можете обрабатывать массив байтов как (4x меньше) массив ints.
  • У вас может быть поле/метод/свойство/событие, все имеют одинаковое имя (непроверено).
  • Вы можете вернуться в блок try из своего собственного блока catch.
  • У вас есть доступ к спецификатору доступа famandassem (protected internalis fam или)
  • Прямой доступ к классу <Module> для определения глобальных функций или инициализатора модуля.

Ответ 1

MSIL допускает перегрузки, которые отличаются только типами возврата из-за

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

или

callvirt int32 ...

Ответ 2

Большинство .Net-языков, включая С# и VB, не используют функцию хвостовой рекурсии кода MSIL.

Рекурсия хвоста - это оптимизация, обычная для функциональных языков. Это происходит, когда метод A заканчивается возвратом значения метода B, так что стек метода может быть освобожден после вызова метода B.

Код MSIL явно поддерживает хвостовую рекурсию, и для некоторых алгоритмов это может быть важной оптимизацией. Но поскольку С# и VB не генерируют инструкции для этого, это должно быть сделано вручную (или с использованием F # или какого-либо другого языка).

Вот пример того, как хвостовая рекурсия может быть реализована вручную на С#:

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 предоставляет эту функцию как часть языка, но с С# или VB он должен быть реализован вручную. (Джиттер также может самостоятельно сделать эту оптимизацию, но это еще одна проблема.)

Ответ 3

Можно объединить модификаторы доступа protected и internal. В С#, если вы пишете protected internal, член доступен из сборки и из производных классов. Через MSIL вы можете получить член, доступный только из производных классов только в сборке. (Я думаю, что это может быть очень полезно!)

Ответ 4

В MSIL вы можете иметь класс, который не может наследовать из System.Object.

Пример кода: скомпилируйте его с помощью ilasm.exe UPDATE: Для предотвращения наследования ассемблера необходимо использовать "/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

Ответ 5

О, я не заметил этого в то время. (Если вы добавите ярлык jon-skeet, это скорее, но я не проверю его так часто.)

Похоже, у вас уже есть неплохие ответы. Кроме того:

  • Вы не можете получить дескриптор в коробке версии типа значения в С#. Вы можете в С++/CLI
  • Вы не можете выполнить попытку/ошибку в С# ( "ошибка" - это как "уловить все и реконструировать в конце блока" или "наконец, но только при сбое" )
  • Существует множество имен, которые запрещены С#, но юридические IL
  • IL позволяет определять собственные конструкторы без параметров для типов значений.
  • Вы не можете определить события с элементом "raise" в С#. (В VB у вас есть для пользовательских событий, но "события по умолчанию" не включают их.)
  • Некоторые преобразования разрешены CLR, но не С#. Если вы пройдете через object в С#, они будут работать. См. uint []/int [] вопрос SO для примера.

Я добавлю к этому, если я подумаю о чем-нибудь еще...

Ответ 7

В IL вы можете бросить и поймать любой тип вообще, а не только типы, полученные из System.Exception.

Ответ 8

IL имеет различие между call и callvirt для вызовов виртуальных методов. Используя первый, вы можете принудительно вызвать виртуальный метод текущего типа статического класса вместо виртуальной функции в типе динамического класса.

С# не может этого сделать:

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

С IL и VB.NET вы можете добавлять фильтры при перехвате исключений, но С# 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 в предложении Catch):

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

Ответ 10

В try/catch вы можете повторно ввести блок try из своего собственного блока catch. Итак, вы можете сделать это:

.try {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

AFAIK вы не можете сделать это на С# или VB

Ответ 11

Насколько я знаю, нет способа сделать инициализаторы модулей (статические конструкторы для всего модуля) непосредственно в С#:

http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx

Ответ 12

Native types
Вы можете работать с родными int и родными неподписанными типами int напрямую (в С# вы можете работать только с IntPtr, который не является тем же.

Transient Pointers
Вы можете играть с переходными указателями, которые являются указателями на управляемые типы, но гарантированно не перемещаются в памяти, так как они не находятся в управляемой куче. Не совсем уверен, как вы могли бы с пользой использовать это без использования неуправляемого кода, но он не был доступен другим языкам напрямую только через такие вещи, как stackalloc.

<Module>
вы можете возиться с классом, если вы этого желаете (вы можете сделать это путем отражения без ИЛ)

.emitbyte

15.4.1.1 Директива .emitbyte MethodBodyItem:: =... |.emitbyte Int32 Эта директива вызывает неподписанное 8-битное значение, которое должно быть выбрано непосредственно в поток CIL метода, в тот момент, когда появляется директива. [Обратите внимание .emitbyte используется для генерирующих тесты. Это не требуется в создании обычных программ. конец примечание]

.entrypoint
У вас есть немного больше гибкости, вы можете применить его к методам, не называемым Main, например.

прочитайте spec Я уверен, что вы найдете еще несколько.

Ответ 13

Вы можете взломать метод переопределения co/contra-variance, который С# не разрешает (это НЕ то же самое, что и общая дисперсия!). У меня есть дополнительная информация об использовании этого здесь, а далее 1 и 2

Ответ 14

Я думаю, что тот, которого я продолжал желать (с совершенно неправильными причинами), был наследованием в Enums. Это не похоже на то, что трудно сделать в SMIL (поскольку Enums - это просто классы), но это не то, что вам нужно сделать синтаксису С#.

Ответ 15

Здесь еще несколько:

  • В делегатах могут быть дополнительные методы экземпляра.
  • Делегаты могут реализовывать интерфейсы.
  • У вас могут быть статические члены в делегатах и ​​интерфейсах.

Ответ 16

20) Вы можете обрабатывать массив байтов в виде (4x меньше) массива ints.

Я использовал это в последнее время для быстрой реализации XOR, поскольку функция CLR xor работает на ints, и мне нужно было делать XOR в потоке байтов.

Полученный код измеряется на ~ 10 раз быстрее, чем эквивалент, сделанный в С# (выполнение XOR для каждого байта).

===

Мне не хватает stackoverflow street credz, чтобы отредактировать вопрос и добавить его в список как # 20, если кто-то еще может это набухать; -)

Ответ 17

Что-то, что обфускаторы используют - вы можете иметь поле/метод/свойство/событие, все имеют одинаковое имя.

Ответ 18

Наследование Enum действительно невозможно:

Вы можете наследовать класс Enum. Но результат не ведет себя как Enum в частности. Он ведет себя не как тип значения, а как обычный класс. Сфера: IsEnum: True, IsValueType: True, IsClass: False

Но это не особенно полезно (если вы не хотите путать человека или самого времени выполнения).

Ответ 19

Вы также можете получить класс из делегата System.Multicast в IL, но вы не можете сделать это в С#:

//Следующее определение класса является незаконным:

открытый класс YourCustomDelegate: MulticastDelegate { }

Ответ 20

Вы также можете определить методы на уровне модуля (aka global) в IL, а С#, напротив, только позволяет вам определять методы, пока они привязаны хотя бы к одному типу.