MSIL'de C # veya VB.NET'te yapamayacağınız şeyler nelerdir? [kapalı]


165

.NET dillerinde yazılmış tüm kodlar MSIL için derlenir, ancak yalnızca doğrudan MSIL kullanarak yapabileceğiniz belirli görevler / işlemler var mı?

MSIL'de C #, VB.NET, F #, j # veya başka herhangi bir .NET dilinden daha kolay iş yapalım.

Şimdiye kadar buna sahibiz:

  1. Kuyruk özyineleme
  2. Genel Ortak / Çelişki
  3. Sadece dönüş tiplerinde farklılık gösteren aşırı yükler
  4. Erişim değiştiricileri geçersiz kıl
  5. System.Object öğesinden devralınamayan bir sınıfa sahip olun
  6. Filtrelenmiş istisnalar (vb.net adresinde yapılabilir)
  7. Geçerli statik sınıf türünde sanal bir yöntem çağırma.
  8. Bir değer türünün kutulu sürümünde bir tanıtıcı alın.
  9. Bir deneme / hata yapın.
  10. Yasak isimlerin kullanımı.
  11. Değer türleri için kendi parametresiz kurucularınızı tanımlayın .
  12. Bir raiseöğeyle olayları tanımlayın .
  13. CLR tarafından izin verilen ancak C # tarafından izin verilmeyen bazı dönüşümler.
  14. Olarak main()yöntem olmayan bir yöntem oluşturun .entrypoint.
  15. doğrudan yerel intve yerel unsigned inttürlerle çalışın .
  16. Geçici işaretçilerle oynayın
  17. MethodBodyItem içindeki emitbyte yönergesi
  18. System.Exception dışı türleri atın ve yakalayın
  19. Numaralandırmayı Devral (Doğrulanmamış)
  20. Bir bayt dizisini (4x daha küçük) bir ints dizisi olarak değerlendirebilirsiniz.
  21. Alan / yöntem / özellik / etkinliğin tümü aynı ada sahip olabilir (Doğrulanmamış).
  22. Kendi catch bloğundan bir try bloğuna geri dönebilirsiniz.
  23. Famandassem erişim belirleyicisine erişiminiz var ( protected internalfam veya assem)
  24. Genel <Module>fonksiyonları tanımlamak için sınıfa veya modül başlatıcısına doğrudan erişim .

17
Mükemmel soru!
Tamas Czinege

5
F # kuyruk özyinelemeyi destekliyor mu bkz: en.wikibooks.org/wiki/F_Sharp_Programming/Recursion
Bas Bossink

4
Numaralandırmalar devralınsın mı? Bazen çok güzel olurdu ..
Jimmy Hoffa

1
Ana yöntem .NET'te M sermayesine sahiptir
Beton Gannet

4
"Yapıcı olmayan şekilde kapalı" iddiası saçmadır. Bu ampirik bir soru.
Jim Balter

Yanıtlar:



29

C # ve VB dahil olmak üzere çoğu .Net dili MSIL kodunun kuyruk özyineleme özelliğini kullanmaz.

Kuyruk özyineleme fonksiyonel dillerde yaygın olan bir optimizasyondur. A yöntemi, B yönteminin çağrısı yapıldıktan sonra A yönteminin yığınının yeniden yerleştirilebileceği şekilde B yönteminin değerini döndürerek sona erdiğinde ortaya çıkar.

MSIL kodu kuyruk özyinelemesini açıkça destekler ve bazı algoritmalar için bu önemli bir optimizasyon olabilir. Ancak C # ve VB bunu yapmak için yönergeler oluşturmadığından, manuel olarak (veya F # veya başka bir dil kullanılarak) yapılmalıdır.

İşte kuyruk özyineleme C # el ile nasıl uygulanabilir bir örnek:

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

Yerel verileri donanım yığından bir yığınla ayrılmış yığın veri yapısına taşıyarak özyinelemeyi kaldırmak yaygın bir uygulamadır. Yukarıda gösterildiği gibi kuyruk çağrısı özyineleme eliminasyonunda, yığın tamamen ortadan kaldırılır, bu da oldukça iyi bir optimizasyondur. Ayrıca, dönüş değerinin uzun bir çağrı zincirinde yürümesi gerekmez, ancak doğrudan döndürülür.

Ancak, yine de, CIL bu özelliği dilin bir parçası olarak sağlar, ancak C # veya VB ile manuel olarak uygulanması gerekir. (Titreşim de bu optimizasyonu kendi başına yapmakta özgürdür, ancak bu başka bir konudur.)


1
F #, MSIL'in kuyruk özyinelemesini kullanmaz, çünkü izin tamiratlarını (vb.) Denetlemek için bir yığın bırakmaması nedeniyle yalnızca tamamen güvenilen (CAS) durumlarda çalışır.
Richard

10
Richard, ne demek istediğinden emin değilim. F # kesinlikle kuyruğu yayar. Çağrı öneki, hemen hemen her yerde. IL'yi şu şekilde inceleyin: "let print x = print_any x".
MichaelGG

1
JIT'in yine de bazı durumlarda kuyruk özyineleme kullanacağına ve bazı durumlarda bunun için açık talebi göz ardı edeceğine inanıyorum. İşlemci mimarisine, IIRC'ye bağlıdır.
Jon Skeet

3
@Abel: İşlemci mimarisi teoride önemsiz olmakla birlikte, farklı mimariler için farklı JIT'lerin .NET'te kuyruk özyineleme için farklı kuralları olduğundan , pratikte ilgisiz değildir . Başka bir deyişle, x86'da patlayan ancak x64'te olmayan bir programa kolayca sahip olabilirsiniz . Kuyruk özyineleme Çünkü edebilir her iki durumda da uygulanacak bu demek değildir olduğunu . Bu sorunun özellikle .NET ile ilgili olduğunu unutmayın.
Jon Skeet

2
C # aslında belirli durumlarda x64 altında kuyruk çağrıları yapar: community.bartdesmet.net/blogs/bart/archive/2010/07/07/… .
Pieter van Ginkel

21

MSIL'de System.Object öğesinden devralınamayan bir sınıfınız olabilir.

Örnek kod: ilasm.exe ile derleyin GÜNCELLEME: Montajcının otomatik devralmasını önlemek için "/ NOAUTOINHERIT" kullanmalısınız.

// 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 - Saygılarımızla, NOAUTOINHERIT'in ne anlama geldiğini anlamama yardım eder misiniz? MSDN, "Hiçbir temel sınıf belirtilmediğinde Object'ten varsayılan devralmayı devre dışı bırakır. Yeni .NET Framework sürüm 2.0'da."
Ramesh

2
@Michael - Soru MSIL ile ilgilidir ve Ortak Ara Dil ile ilgili değildir. Bunun CIL'de mümkün olmayabileceğini kabul ediyorum, ancak yine de MSIL ile çalışıyor
Ramesh

4
@Ramesh: Hata! Kesinlikle haklısın. Bu noktada standart spesifikasyonu bozduğunu ve kullanılmaması gerektiğini söyleyebilirim. Reflektör montajı bile yüklemiyor. Ancak, olabilir ILASM ile yapılabilir. Dünyada neden orada olduğunu merak ediyorum.
Jon Skeet

4
(Ah, yorumumdan sonra / noautoinherit bitinin eklendiğini görüyorum. En azından daha önce fark etmeme konusunda biraz daha iyi hissediyorum ...)
Jon Skeet

1
Ben derlemek en azından Windows üzerinde .NET 4.5.2 üzerinde derlemek ama yürütmez ( TypeLoadException). PEVerify şunu döndürür: [MD]: Hata: Object sınıfı değil, bir Interface olmayan TypeDef Nil belirtecini genişletir.
xanatos

20

Bu birleştirmek mümkün protectedve internalerişim değiştiricileri. C # 'da yazarsanız protected internalderlemeden ve türetilmiş sınıflardan erişilebilir. MSIL ile yalnızca derleme içinde türetilmiş sınıflardan erişilebilen bir üye alabilirsiniz . (Bence bu oldukça faydalı olabilir!)


4
Şimdi C # 7.1'de ( github.com/dotnet/csharplang/issues/37 ) uygulanacak bir adaydır , erişim değiştiriciprivate protected
Happypig375

5
C # 7.2'nin bir parçası olarak yayınlandı: blogs.msdn.microsoft.com/dotnet/2017/11/15/…
Joe Sewell

18

Ooh, o zaman bunu fark etmedim. (Jon-skeet etiketini eklerseniz, bu daha olasıdır, ancak sık sık kontrol etmiyorum.)

Görünüşe göre çok iyi cevaplarınız var. Ek olarak:

  • C # 'daki bir değer türünün kutulu sürümünde tanıtıcı alamazsınız. C ++ / CLI içinde yapabilirsiniz
  • C # bir deneme / hata yapamazsınız ("hata" bir "her şeyi yakalamak ve blok sonunda tekrar" veya "sonunda ama sadece başarısızlık" gibi)
  • C # ama yasal IL tarafından yasaklanan birçok isim var
  • IL, değer türleri için kendi parametresiz kurucularınızı tanımlamanızı sağlar .
  • C # 'da "yükselt" öğesiyle olayları tanımlayamazsınız. (VB sen var özel etkinlikler için , ancak "varsayılan" etkinlikler bir tane içermez.)
  • Bazı dönüşümlere CLR tarafından izin verilir, ancak C # tarafından izin verilmez. objectC # ile geçerseniz , bunlar bazen işe yarayacaktır. Bir bakınız uint [] / int [] SO soru bir örnek için.

Başka bir şey düşünürsem buna ekleyeceğim ...


3
Ah jon-skeet etiketi, bir şey eksik olduğumu biliyordum!
Binoj Antony

Yasadışı tanımlayıcı adını kullanmak için C # ile @ öneki ekleyebilirsiniz
George Polevoy

4
@George: Bu anahtar kelimeler için geçerlidir, ancak geçerli IL adları için geçerli değildir. <>aC # ... 'da bir isim olarak belirtmeyi deneyin
Jon Skeet


14

IL'de sadece türetilmiş türleri değil, herhangi bir türü atabilir ve yakalayabilirsiniz System.Exception.


6
Bunu C # ile de yapabilirsiniz, catch-deyiminde parantez ile try/ catchparantez olmadan da, İstisna benzeri olmayan istisnaları da yakalayacaksınız. Bununla birlikte, atma gerçekten de sadece miras aldığınızda mümkündür Exception.
Abel

@Abel Eğer ona bakamazsan bir şey yakaladığını söyleyemezsin.
Jim Balter

2
@JimBalter yakalamazsanız uygulama çöküyor. Yakalarsanız, uygulama çökmez. Bu nedenle, istisna nesnesine atıfta bulunmak onu yakalamaktan farklıdır.
Daniel Earwicker

Lol! Uygulama sonlandırma veya devam etme arasındaki fark, bilgiçlik mi? Şimdi sanırım her şeyi duymuş olabilirim.
Daniel Earwicker

İlginçtir, CLR artık bunu yapmanıza izin vermiyor. Varsayılan olarak, RuntimeWrappedException içinde istisnai olmayan nesneleri sarar .
Jwosty

10

IL, sanal yöntem çağrıları arasında callve callvirtsanal yöntem çağrıları arasında ayrım yapar . Birincisini kullanarak geçerli statik sınıf türünün sanal bir yöntemini çağırmayı zorlayabilirsiniz dinamik sınıf türündeki sanal işlev yerine .

C # bunu yapmanın bir yolu yoktur:

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 gibi VB de MyClass.Method()sözdizimini kullanarak sanal olmayan çağrılar verebilir . Yukarıda, bu olurdu MyClass.ToString().


9

Bir try / catch'de, try bloğunu kendi catch bloğundan tekrar girebilirsiniz. Yani, bunu yapabilirsiniz:

.try {
    // ...

  MidTry:
    // ...

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

RestOfMethod:
    // ...

AFAIK bunu C # veya VB'de yapamazsınız


3
Bunun neden atlandığını anlayabiliyorum - Farklı bir kokusu varGOTO
Temel

3
Çok gibi Sesler On Error Resume Sonraki vb.NET'te
Thomas Weller

1
Bu aslında VB.NET'te yapılabilir. Git deyimi gelen dallara ayrılabilir Catchonun içinTry . Online test kodu çalıştırın burada .
mbomb007

9

IL ve VB.NET ile istisnaları yakalarken filtreler ekleyebilirsiniz, ancak C # v3 bu özelliği desteklemez.

Bu VB.NET örneği alınır http://blogs.msdn.com/clrteam/archive/2009/02/05/catch-rethrow-and-filters-why-you-should-care.aspx (not When ShouldCatch(ex) = Trueiçinde Yakalama maddesi):

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

17
Lütfen çıkarın, = Truegözlerimi kanıyor !
Konrad Rudolph

Neden? Bu VB'dir, C # değildir, dolayısıyla = / == problem yoktur. ;-)
peSHIr

C # "atmak" yapabilir, böylece aynı sonuç elde edilebilir.
Frank Schwieterman

8
paSHIr, onun redudancy'den bahsettiğine inanıyorum
LegendLength

5
@Frank Schwieterman: Bir istisnayı yakalamak ve geri almak arasında, onu yakalamaktan vazgeçmek arasında bir fark var. Filtreler yuvalanmış "son olarak" ifadelerinden önce çalışır, bu nedenle filtre çalıştırıldığında istisnaya neden olan koşullar yine de kalır. Biri nispeten sessizce yakalamak isteyecek önemli sayıda SocketException atılmasını beklerse, ancak bunlardan birkaçı sorun sinyalini verir, sorunlu bir atıldığında durumu inceleyebilmek çok yararlı olabilir.
supercat


7

Native types
Yerel int ve yerli imzasız int türleriyle doğrudan çalışabilirsiniz (c # 'da yalnızca aynı olmayan bir IntPtr üzerinde çalışabilirsiniz.

Transient Pointers
Yönetilen türlere işaret eden, ancak yönetilen yığında olmadıkları için bellekte hareket etmemeleri garanti edilen geçici işaretçilerle oynayabilirsiniz. Yönetilmeyen kodla uğraşmadan bunu nasıl kullanabileceğinizden tam olarak emin değilim, ancak diğer dillere doğrudan stackalloc gibi şeyler aracılığıyla maruz kalmıyor.

<Module>
Eğer arzu ederseniz sınıfla uğraşabilirsiniz (bunu IL'ye ihtiyaç duymadan yansıma ile yapabilirsiniz)

.emitbyte

15.4.1.1 .emitbyte yönergesi MethodBodyItem :: =… | .emitbyte Int32 Bu yönerge, yönerge göründüğü noktada doğrudan yöntemin CIL akışına işaretsiz bir 8 bit değerinin yayılmasına neden olur. [Not: .emitbyte yönergesi testler oluşturmak için kullanılır. Düzenli programlar üretilmesine gerek yoktur. son not]

.entrypoint
Bu konuda biraz daha esnekliğiniz var, örneğin Ana olarak adlandırılmayan yöntemlere uygulayabilirsiniz.

şartnameyi oku eminim birkaç tane daha bulacaksınız.


+1, bazı gizli taşlar burada. Bunun <Module>, global yöntemleri (VB'nin yaptığı gibi) kabul eden diller için özel bir sınıf olarak kastedildiğini, ancak C # doğrudan erişemediğini unutmayın.
Abel

Geçici işaretçiler çok kullanışlı bir tür gibi görünüyorlar; değişebilir yapılara yapılan itirazların çoğu ihmallerinden kaynaklanmaktadır. Örneğin, "DictOfPoints (anahtar) .X = 5;" DictOfPoints (anahtar), yapıya göre değerin kopyalanması yerine bir yapıya geçici bir işaretçi döndürdüğünde işe yarayabilir.
supercat

geçici bir işaretçi ile çalışmazsa, superplay, söz konusu veriler yığın üzerinde olabilir. ne istiyorsun ref burada Eric hakkında görüşmeler döner: blogs.msdn.com/b/ericlippert/archive/2011/06/23/…
ShuggyCoUk

@ShuggyCoUk: İlginç. Umarım Eric "DictOfPoints (anahtar) .X = 5;" çalışmak için yapılabilir. Şu anda biri yalnızca DictOfPoints kodunu yalnızca Point (veya başka bir tip) tipiyle çalışmak üzere kodlamak istiyorsa, neredeyse bu işi yapabilir, ancak bu bir acıdır. BTW, görmek istediğim bir şey, bir açık uçlu genel fonksiyon yazabileceğiniz bir araç olurdu, örneğin DoStuff <...> (someParams, ActionByRef <moreParams, ...>, ...) gerektiğinde genişleyebilir. Sanırım bunu MSIL'de yapmanın bir yolu olurdu; bazı derleyici yardımı ile ...
supercat

@ShuggyCoUk: ... başkalarının referansa yapacağı her şey yapıldıktan sonra bazı özelliklerin çalıştırabileceği ek bonus ile birlikte referans özelliklerine sahip olmanın başka bir yolunu sağlayacaktır.
supercat

6

C # 'nın izin vermediği co / contra varyans yöntemini geçersiz kılma yöntemini hackleyebilirsiniz (bu, genel varyans ile aynı DEĞİLDİR!). Bunu burada uygulamak hakkında daha fazla bilgiye sahibim ve bölüm 1 ve 2


4

Bence (tamamen yanlış nedenlerle) dilediğim kişi Enum'da kalıtımdı. SMIL'de yapmak zor bir şey gibi görünmüyor (Enum'lar sadece sınıflar olduğu için), ancak C # sözdiziminin yapmanızı istediği bir şey değil.


4

İşte biraz daha:

  1. Temsilcilerde fazladan örnek yöntemleriniz olabilir.
  2. Delegeler arabirimler uygulayabilir.
  3. Temsilci ve arabirimlerde statik üyeleriniz olabilir.

3

20) Bir bayt dizisini (4x daha küçük) bir ints dizisi olarak değerlendirebilirsiniz.

Son zamanlarda hızlı bir XOR uygulaması yapmak için kullandım, çünkü CLR xor işlevi ints üzerinde çalışıyor ve bir bayt akışında XOR yapmam gerekiyordu.

Ortaya çıkan kod, C # 'da yapılan eşdeğerden ~ 10x daha hızlı olarak ölçülmüştür (her baytta XOR yapıyor).

===

Soruyu düzenlemek ve # 20 olarak listeye eklemek için yeterli stackoverflow sokak credzim yok, başka biri olabilirse şişebilir ;-)


3
IL'ye dalmak yerine, bunu güvensiz işaretçilerle başarabilirdiniz. Sınır kontrol etmediği için hızlı ve belki de daha hızlı olacağını düşünürdüm.
P Daddy

3

Gizleyicilerin kullandığı bir şey - bir alan / yöntem / özellik / etkinlik aynı ada sahip olabilir.


1
Siteme bir örnek ekledim : jasonhaley.com/files/NameTestA.zip Bu zip dosyasında IL ve aşağıdaki tüm 'A' ile bir sınıf içeren bir exe var: sınıf adı A -Event adlı A -Method adlı A -Metika A-2 adlı AI alanları, size işaret etmek için iyi bir referans bulamıyor, ancak muhtemelen ecma 335 spec veya Serge Lidin'in kitabında okudum.
Jason Haley

2

Enum mirası gerçekten mümkün değildir:

Bir Enum sınıfından miras alabilirsiniz. Ancak sonuç özellikle bir Enum gibi davranmaz. Bir değer türü gibi değil, sıradan bir sınıf gibi davranır. Srange şey: IsEnum: True, IsValueType: True, IsClass: False

Ancak bu özellikle yararlı değildir (bir kişiyi veya çalışma zamanının kendisini karıştırmak istemiyorsanız).


2

IL'de System.Multicast delegesi olan bir sınıf da türetebilirsiniz, ancak bunu C # ile yapamazsınız:

// Aşağıdaki sınıf tanımı yasadışı:

herkese açık sınıf YourCustomDelegate: MulticastDelegate {}


1

Ayrıca, IL'de modül düzeyinde (küresel olarak da bilinir) yöntemler tanımlayabilirsiniz ve aksine C #, yalnızca en az bir türe bağlı oldukları sürece yöntemleri tanımlamanıza izin verir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.