Що ви можете зробити в MSIL, чого не можете зробити в C # або VB.NET? [зачинено]


165

Весь код, написаний мовами .NET, компілюється в MSIL, але чи існують конкретні завдання / операції, які ви можете виконувати лише безпосередньо за допомогою MSIL?

Дозвольте нам також зробити легше в MSIL, ніж C #, VB.NET, F #, j # або будь-якій іншій мові .NET.

Поки що ми маємо це:

  1. Рекурсія хвоста
  2. Generic Co / суперечності
  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
  18. Киньте і ловіть не типи System.Exception
  19. Спадкові перерахунки (неперевірені)
  20. Ви можете трактувати масив байтів як (на 4 рази менший) масив вкладень.
  21. Ви можете мати поле / метод / властивість / подія, всі мають однакове ім’я (Неперевірене).
  22. Ви можете відключитися на спробу блоку з власного блоку лову.
  23. Ви маєте доступ до специфікатора доступу famandassem ( protected internalє fam або assem)
  24. Прямий доступ до <Module>класу для визначення глобальних функцій або ініціалізатор модуля.

17
Відмінне запитання!
Тамас Цінеге

5
F # не підтримує хвостові рекурсії див. En.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink

4
Спадкові перерахунки? Це було б так приємно іноді ..
Джиммі Хоффа

1
Основний метод має капітал M у .NET
Concrete Gannet

4
"Закритий як неконструктивний" позов є абсурдним. Це емпіричне питання.
Джим Балтер

Відповіді:


34

MSIL дозволяє виконувати перевантаження, які відрізняються лише типом повернення через

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

або

callvirt int32 ...

5
Звідки ви знаєте подібні речі? :)
Геррі Шенк


8
Це круто. Хто не хотів робити зворотних перевантажень?
Джиммі Хоффа

12
Якщо два способи ідентичні, окрім типу повернення, чи можна викликати з C # або vb.net?
supercat

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 # звичайно випромінює хвіст. префікс виклику, майже у всьому місці. Вивчіть ІЛ для цього: "нехай надрукувати 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
@Jon Skeet - З усією повагою, будь ласка, допоможіть мені зрозуміти, що означає NOAUTOINHERIT. MSDN вказує "Вимикає успадкування за замовчуванням від Object, коли не вказаний базовий клас. Нове у .NET Framework версії 2.0."
Рамеш

2
@Michael - Питання стосується MSIL, а не загальної проміжної мови. Я згоден, це може бути неможливо в CIL, але він все ще працює з MSIL
Рамеш,

4
@Ramesh: На жаль, ви абсолютно праві. Я б сказав, що в цей момент воно порушує стандартні характеристики, і його не слід використовувати. Рефлектор навіть не завантажує припустимо. Однак це можна зробити ілазмом. Цікаво, чому на землі він є.
Джон Скіт

4
(Ах, я бачу, що біт / noautoinherit був доданий після мого коментаря. Принаймні, я відчуваю себе дещо краще, якщо не усвідомлювати цього раніше ...)
Джон Скіт,

1
Додам, що принаймні на .NET 4.5.2 у Windows він компілюється, але не виконує ( TypeLoadException). PEVerify повертає: [MD]: Помилка: TypeDef, який не є інтерфейсом, і не клас Object, розширює маркер Nil.
xanatos

20

Можна комбінувати модифікатори protectedта internalотримати доступ до них. У C #, якщо ви пишете, protected internalчлен доступний із складання та з похідних класів. Via MSIL ви можете отримати елемент , який доступний з похідних класів в збірці тільки . (Я думаю, що це може бути дуже корисно!)


4
Це кандидат, який зараз можна реалізувати на C # 7.1 ( github.com/dotnet/csharplang/isissue/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 [] SO .

Я додам до цього, якщо думаю про щось інше ...


3
Ах тег jon-skeet, я знав, що мені щось не вистачає!
Біной Антоній

Щоб використовувати ім’я незаконного ідентифікатора, ви можете встановити його за допомогою @ в C #
Джордж Полевой

4
@George: Це працює для ключових слів, але не для всіх дійсних імен IL. Спробуйте вказати <>aяк ім'я в C # ...
Джон Скіт


14

В IL ви можете кидати та ловити будь-який тип взагалі, а не лише типи, що походять від них System.Exception.


6
Ви можете це зробити і в C #, з дужками в дужці-операторі try/ 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 {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

AFAIK, ви не можете цього зробити в C # або VB


3
Я можу зрозуміти, чому це було опущено - Він має виразний запахGOTO
Basic

3
Звучить як On Error Resume Next у VB.NET
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
Будь ласка, усуньте = True, це змушує мої очі кровоточити!
Конрад Рудольф

Чому? Це VB, а не C #, тому проблем із проблемами = / == не існує. ;-)
peSHIr

Добре c # може зробити "кидок", тому можна досягти такого ж результату.
Френк Швітерман

8
paSHIr, я вважаю, що він говорив про його зменшення
LegendLength

5
@Frank Schwieterman: Існує різниця між ловом і повторним викиданням винятку, порівняно з триманням його на лову. Фільтри запускаються перед будь-якими вкладеними операторами "нарешті", тому обставини, що спричинили виняток, все ще існуватимуть під час запуску фільтра. Якщо ви очікуєте, що значна кількість SocketException буде викинута, він захоче зловити відносно мовчки, але деякі з них будуть сигналізувати про неприємності, будучи спроможними вивчити стан, коли проблемна кидається, може бути дуже корисною.
supercat


7

Native types
Ви можете працювати безпосередньо з нативними int та нативними типами неподписаних 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 (ключ) .X = 5;" було б працездатним, якби DictOfPoints (ключ) повернув перехідний покажчик до структури, а не копіював структуру за значенням.
supercat

@supercat, який би не працював з тимчасовим вказівником, ці дані можуть бути в купі. що ви хочете - це повернення, про яке Ерік розповідає тут: blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
ShuggyCoUk

@ShuggyCoUk: Цікаво. Я дуже сподіваюся, що Еріка можна переконати надати засоби, за допомогою яких "DictOfPoints (ключ) .X = 5;" можна було змусити працювати. Зараз, якщо хочеться жорстко закодувати DictOfPoints, щоб працювати виключно з типом Point (або яким-небудь іншим типом), можна майже зробити це, але це біль. До речі, одне, що я хотів би бачити, - це засіб, за допомогою якого можна було б записати родову функцію відкритого типу, наприклад, DoStuff <...> (деякіПарами, ActionByRef <moreParams, ...>, ...), які може розширюватися у міру необхідності. Я здогадуюсь, був би якийсь спосіб зробити це в MSIL; з деякою допомогою компілятора ...
supercat

@ShuggyCoUk: ... це забезпечило б інший спосіб наявності властивостей посилань, з додатковим бонусом, який деяка власність може працювати після того, як буде зроблено сторонніх людей до посилання.
supercat

6

Ви можете зламати метод переосмислення ко / контра-дисперсії, що C # не дозволяє (це НЕ те саме, що загальна дисперсія!). Я отримав більше інформації про реалізацію цього тут , а також частини 1 і 2


4

Я думаю, що той, кого я бажав (з цілком неправильних причин), був спадщиною в «Енумс». Здається, це не складно зробити в SMIL (оскільки Enums - це просто класи), але це не те, що синтаксис C # хоче, щоб ти робив.


4

Ось ще кілька:

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

3

20) Ви можете трактувати масив байтів як (на 4 рази менший) масив внів.

Нещодавно я використав це для швидкої реалізації XOR, оскільки функція xr CLR працює на ints, і мені потрібно було зробити XOR у потоці байтів.

Отриманий код вимірюється на ~ 10x швидше, ніж еквівалент, виконаний в C # (роблячи XOR на кожен байт).

===

У мене не вистачає stackoverflow street kredz, щоб відредагувати питання та додати це до списку як №20, якщо хтось інший міг би це розбухнути ;-)


3
Замість того, щоб зануритися в IL, ви могли це зробити за допомогою небезпечних покажчиків. Я б міг уявити, що це було б так само швидко, а можливо, і швидше, оскільки це не проводить меж перевірки.
P тато

3

Щось використовує обфускатор - у вас може бути поле / метод / властивість / подія, всі мають однакову назву.


1
Я розміщую зразок на своєму веб-сайті: jasonhaley.com/files/NameTestA.zip У цьому zip є IL та exe, який містить клас із таким самим "A": ім'я класу A -Event A -Метод з назвою A -Property з назвою A -2 Поля з ім'ям AI не може знайти хорошого посилання на те, щоб на тобі вказати, хоча я, ймовірно, читав його або в специфікації ecma 335, або в книзі Сержа Лідіна.
Джейсон Хейлі

2

Успадкування Enum насправді неможливо:

Ви можете успадкувати з класу Enum. Але результат не так поводиться, як Енум. Він поводиться навіть не як тип значення, а як звичайний клас. Дивна річ: IsEnum: True, IsValueType: True, IsClass: False

Але це не особливо корисно (якщо ви не хочете заплутати людину чи сам час виконання).


2

Ви також можете отримати клас від делегата System.Multicast в IL, але ви не можете цього зробити в C #:

// Наступне визначення класу є незаконним:

публічний клас YourCustomDelegate: MulticastDelegate {}


1

Ви також можете визначити методи на рівні модуля (він же глобальний) в IL, а C #, навпаки, дозволяє лише визначати методи, якщо вони прикріплені принаймні до одного типу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.