Что вы можете сделать в MSIL, что вы не можете сделать в C # или VB.NET? [закрыто]

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. Сделайте попытку / ошибку.
  10. Использование запрещенных имен.
  11. Определите свои собственные конструкторы без параметров для типов значений .
  12. Определите события с raiseэлементом.
  13. Некоторые преобразования разрешены CLR, но не C #.
  14. Сделайте не main()метод как .entrypoint.
  15. работать с нативным intи нативным unsigned intтипами напрямую.
  16. Играть с переходными указателями
  17. директива emitbyte в MethodBodyItem
  18. Бросать и ловить не System.Exception типы
  19. Inherit Enums (не проверено)
  20. Вы можете рассматривать массив байтов как (в 4 раза меньший) массив целых.
  21. У вас может быть поле / метод / свойство / событие, все они имеют одно и то же имя (не проверено).
  22. Вы можете перейти обратно в блок try из своего собственного блока catch.
  23. У вас есть доступ к спецификатору доступа famandassem ( protected internalявляется fam или сборкой)
  24. Прямой доступ к <Module>классу для определения глобальных функций или инициализатора модуля.
Биной ​​Антони
источник
17
Отличный вопрос!
Тамас Чинеге
5
F # поддерживает хвостовую рекурсию, см .: en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink
4
Наследовать перечисления? Это было бы так приятно иногда ..
Джимми Хоффа
1
Основной метод имеет заглавную букву М в .NET
Бетонные боровы
4
«Закрытое как неконструктивное» утверждение абсурдно. Это эмпирический вопрос.
Джим Балтер

Ответы:

34

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

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

или

callvirt int32 ...
Антон Гоголев
источник
5
Откуда ты знаешь такие вещи? :)
Джерри Шенк
8
Это круто. Кто не хотел совершать возвратные перегрузки?
Джимми Хоффа
12
Если два метода идентичны, за исключением типа возвращаемого значения, могут ли они быть вызваны из C # или vb.net?
Суперкат
29

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

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

Код 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.
Джон Скит
3
@Abel: Хотя архитектура процессора не имеет значения в теории , на практике она не имеет значения, поскольку разные JIT для разных архитектур имеют разные правила для хвостовой рекурсии в .NET. Другими словами, вы могли бы очень легко иметь программу, которая взорвалась на x86, но не на x64. То, что хвостовая рекурсия может быть реализована в обоих случаях, не означает, что это так . Обратите внимание, что этот вопрос касается конкретно .NET.
Джон Скит
2
C # на самом деле выполняет хвостовые вызовы под x64 в определенных случаях: community.bartdesmet.net/blogs/bart/archive/2010/07/07/… .
Питер ван Гинкель
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 указано «Отключает наследование по умолчанию от объекта, если базовый класс не указан. Новое в .NET Framework версии 2.0».
Рамеш
2
@Michael - Вопрос касается MSIL, а не Common Intermediate Language. Я согласен, что это невозможно в CIL, но все еще работает с MSIL
Рамеш
4
@ Рамеш: Ой, ты абсолютно прав. Я бы сказал, что в этот момент он нарушает стандартную спецификацию и не должен использоваться. Отражатель даже не загружает ассемблер. Тем не менее, это может быть сделано с ilasm. Интересно, с какой стати это там.
Джон Скит
4
(Ах, я вижу, что бит / noautoinherit был добавлен после моего комментария. По крайней мере, я чувствую себя немного лучше, не осознавая этого раньше ...)
Джон Скит
1
Я добавлю это, по крайней мере, на .NET 4.5.2 в Windows, он компилируется, но не выполняется ( TypeLoadException). PEVerify возвращает: [MD]: Ошибка: TypeDef, который не является интерфейсом и не классом объекта, расширяет маркер Nil.
Ксанатос
20

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

yatima2975
источник
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 # («ошибка» - это как «поймать все и перебросить в конце блока» или «наконец, но только при ошибке»)
  • Есть много имен, которые запрещены C #, но законный IL
  • IL позволяет вам определять ваши собственные конструкторы без параметров для типов значений .
  • Вы не можете определять события с помощью элемента "поднимать" в C #. (В VB у вас есть для пользовательских событий, но события «по умолчанию» не включают один.)
  • Некоторые преобразования разрешены CLR, но не C #. Если вы пройдете через objectC #, это иногда будет работать. Смотрите uint [] / int [] ТАК вопрос для примера.

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

Джон Скит
источник
3
Ах, тег jon -keet, я знал, что что-то упустил!
Биной ​​Энтони
Чтобы использовать недопустимое имя идентификатора, вы можете использовать префикс @ в C #
Джордж Полевой
4
@ Джордж: Это работает для ключевых слов, но не для всех действительных имен IL. Попробуйте указать <>aимя в C # ...
Джон Скит,
17

CLR уже поддерживает стандартную co / contravariance, но C # не получает эту функцию до 4.0

ermau
источник
Можете ли вы предоставить ссылку с дополнительной информацией по этому вопросу?
Биной ​​Антоний
14

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

Дэниел Уорвикер
источник
6
Вы можете сделать это и в C #, с try/ catchбез круглых скобок в операторе catch вы также поймаете исключения, не похожие на исключения. Бросать, однако, действительно возможно только тогда, когда вы наследуете от Exception.
Авель
@Abel Вы вряд ли сможете сказать, что поймали что-то, если не можете сослаться на это.
Джим Балтер
2
@JimBalter, если вы не поймете это, приложение вылетает. Если вы поймали его, приложение не падает. Таким образом, ссылка на объект исключения отличается от его перехвата.
Даниэль Эрвикер
Ржунимагу! Различие между приложением, заканчивающимся или продолжающимся, является педантизмом? Теперь я думаю, что, возможно, все слышал.
Даниэль Уорвикер
Интересно, что 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 вы можете повторно войти в блок try из его собственного блока catch. Итак, вы можете сделать это:

.try {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

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

thecoop
источник
3
Я могу понять, почему это было опущено - у него отчетливый запахGOTO
Basic
3
Звучит так же, как при возобновлении ошибки Далее в VB.NET
Томас Веллер
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
Пожалуйста, удалите = True, это заставляет мои глаза кровоточить!
Конрад Рудольф
Зачем? Это VB, а не C #, поэтому проблем с = / == не существует. ;-)
peSHIr
Хорошо, C # может сделать «throw», так что тот же результат может быть достигнут.
Фрэнк Швитерман
8
ПАШИР, я думаю, что он говорил о его избыточности
LegendLength
5
@Frank Schwieterman: Есть разница между перехватом и повторным выбросом исключения и его задержкой. Фильтры запускаются перед любыми вложенными операторами "finally", поэтому обстоятельства, вызвавшие исключение, все еще будут существовать при запуске фильтра. Если кто-то ожидает, что будет выдано значительное количество исключений SocketException, он захочет сравнительно тихо отловить, но некоторые из них будут сигнализировать о проблеме, и возможность проверки состояния при возникновении проблемной ситуации может быть очень полезной.
суперкат
7

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

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

<Module>
вы можете возиться с классом, если пожелаете (вы можете сделать это с помощью рефлексии, не нуждаясь в IL)

.emitbyte

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

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

прочитайте спецификацию, я уверен, вы найдете еще несколько.

ShuggyCoUk
источник
+1, некоторые скрытые драгоценные камни здесь. Обратите внимание, что <Module>это подразумевается как специальный класс для языков, которые принимают глобальные методы (как VB), но на самом деле C # не может получить к нему прямой доступ.
Авель
Переходные указатели кажутся очень полезными; многие из возражений против изменчивых структур проистекают из их упущения. Например, «DictOfPoints (key) .X = 5;» было бы работоспособным, если бы DictOfPoints (key) возвращал временный указатель на структуру, а не копировал структуру по значению.
суперкат
@supercat, который не будет работать с временным указателем, данные могут быть в куче. что вы хотите, так это рефери, о которой говорит Эрик, здесь: blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
ShuggyCoUk
@ShuggyCoUk: Интересно. Я действительно надеюсь, что Эрика можно убедить предоставить средство, с помощью которого "DictOfPoints (key) .X = 5;" можно заставить работать. Прямо сейчас, если кто-то хочет жестко закодировать DictOfPoints для работы исключительно с типом Point (или каким-то другим конкретным типом), можно почти заставить это работать, но это боль. Кстати, одна вещь, которую я хотел бы увидеть, - это средство, с помощью которого можно написать универсальную функцию с открытым концом, например DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...), которая может расширяться по мере необходимости. Я предполагаю, что был бы какой-то способ сделать это в MSIL; с некоторой помощью компилятора ...
суперкат
@ShuggyCoUk: ... это обеспечило бы еще один способ иметь свойства по ссылке, с дополнительным бонусом, что некоторые свойства могут работать после того, как все, что посторонние будут делать со ссылкой, было сделано.
суперкат
6

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

оборота thecoop
источник
4

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

Шалом Креймер
источник
4

Вот еще немного:

  1. Вы можете иметь дополнительные методы экземпляра в делегатах.
  2. Делегаты могут реализовывать интерфейсы.
  3. Вы можете иметь статические члены в делегатах и ​​интерфейсах.
leppie
источник
3

20) Вы можете рассматривать массив байтов как (в 4 раза меньший) массив целых.

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

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

===

У меня нет достаточно стека и потока stackoverflow, чтобы отредактировать вопрос и добавить его в список под номером # 20, если бы кто-то еще мог, это было бы здорово ;-)

Розенфельд
источник
3
Вместо того, чтобы окунуться в IL, вы могли бы сделать это с помощью небезопасных указателей. Я полагаю, что это было бы так же быстро, и, возможно, быстрее, так как это не сделало бы проверку границ.
P Daddy
3

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

Джейсон Хейли
источник
1
Я разместил образец на своем сайте: jasonhaley.com/files/NameTestA.zip. В этом zip есть IL и exe, который содержит класс со следующим тем же «A»: -class name - A -Event named A - Метод с именем A - Свойство с именем A -2 Поля с именем AI не могут найти хорошую ссылку, на которую можно указать, хотя я, вероятно, читал это либо в спецификации ecma 335, либо в книге Сержа Лидина.
Джейсон Хейли
2

Enum наследование не возможно:

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

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

Zotta
источник
2

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

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

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

plaureano
источник
1

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

plaureano
источник