Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Not
Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.
Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar,ilgili
Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek için
Özet
Bu teklif, C# dilinde bugün etkin bir şekilde erişilemeyen veya hiç erişilemeyen IL opcode'larını açığa çıkaran dil yapıları sağlar: ldftn ve calli. Bu IL opcode'ları yüksek performanslı kodlarda önemli olabilir ve geliştiricilerin bunlara erişmek için verimli bir yönteme ihtiyacı vardır.
Motivasyon
Bu özelliğin motivasyonları ve arka planı aşağıdaki sorunda açıklanmıştır (özelliğin olası bir uygulaması olduğu gibi):
Bu, derleyici intrinsics için alternatif bir tasarım teklifidir.
Ayrıntılı Tasarım
İşlev işaretçileri
Dil, delegate* söz dizimini kullanarak işlev işaretçilerinin bildirimini sağlar. Söz diziminin tamamı sonraki bölümde ayrıntılı olarak açıklanmıştır, ancak Func ve Action tür bildirimleri tarafından kullanılan söz dizimine benzemesi amaçlanmıştır.
unsafe class Example
{
void M(Action<int> a, delegate*<int, void> f)
{
a(42);
f(42);
}
}
Bu türler, ECMA-335'te özetlendiği gibi işlev işaretçisi türü kullanılarak temsil edilir. Bir delegate* çağrılması, calli yönteminde bir delegate çağrısı callvirt kullanırken Invoke kullanacağı anlamına gelir.
Sözdizimi açısından çağırmanın her iki yapı için de aynı olduğunu söyleyebiliriz.
Yöntem işaretçilerinin ECMA-335 tanımı, tür imzası (bölüm 7.1) kapsamında çağırma kuralını içerir.
Varsayılan çağırma kuralı managedolacaktır. Yönetilmeyen çağrı kuralları, çalışma zamanı platformunun varsayılanını kullanacak şekilde unmanaged söz diziminin ardından bir delegate* anahtar sözcüğü ekleyerek belirtilebilir. Belirli yönetilmeyen kurallar daha sonra unmanaged ad alanında CallConv ile başlayan herhangi bir tür belirtilerek System.Runtime.CompilerServices anahtar sözcüğüne köşeli ayraçlar halinde belirtilebilir ve CallConv ön eki bırakılabilir. Bu türler programın çekirdek kitaplığından gelmelidir ve geçerli birleşimler kümesi platforma bağımlıdır.
//This method has a managed calling convention. This is the same as leaving the managed keyword off.
delegate* managed<int, int>;
// This method will be invoked using whatever the default unmanaged calling convention on the runtime
// platform is. This is platform and architecture dependent and is determined by the CLR at runtime.
delegate* unmanaged<int, int>;
// This method will be invoked using the cdecl calling convention
// Cdecl maps to System.Runtime.CompilerServices.CallConvCdecl
delegate* unmanaged[Cdecl] <int, int>;
// This method will be invoked using the stdcall calling convention, and suppresses GC transition
// Stdcall maps to System.Runtime.CompilerServices.CallConvStdcall
// SuppressGCTransition maps to System.Runtime.CompilerServices.CallConvSuppressGCTransition
delegate* unmanaged[Stdcall, SuppressGCTransition] <int, int>;
delegate* türleri arasındaki dönüştürmeler, çağırma kuralı dahil olmak üzere imzalarına göre yapılır.
unsafe class Example {
void Conversions() {
delegate*<int, int, int> p1 = ...;
delegate* managed<int, int, int> p2 = ...;
delegate* unmanaged<int, int, int> p3 = ...;
p1 = p2; // okay p1 and p2 have compatible signatures
Console.WriteLine(p2 == p1); // True
p2 = p3; // error: calling conventions are incompatible
}
}
delegate* türü, standart bir işaretçi türünün tüm özelliklerine ve kısıtlamalarına sahip olduğu anlamına gelen bir işaretçi türüdür:
- Yalnızca
unsafebağlamında geçerlidir. -
delegate*parametresi veya dönüş türü içeren yöntemler yalnızcaunsafebağlamından çağrılabilir. -
objectolarak dönüştürülemez. - Genel bağımsız değişken olarak kullanılamaz.
-
delegate*, örtük olarakvoid*'e dönüştürülebilir. - açıkça
void*'dendelegate*dönüştürebilir.
Kısıtlama -ları:
- Özel öznitelikler bir
delegate*veya öğelerinin herhangi birine uygulanamaz. -
delegate*parametresiparamsolarak işaretlenemez -
delegate*türü, normal bir işaretçi türünün tüm kısıtlamalarına sahiptir. - İşaretçi aritmetiği doğrudan işlev işaretçisi türlerinde gerçekleştirilemez.
İşlev işaretçisi söz dizimi
tam işlev işaretçisi söz dizimi aşağıdaki dil bilgisi ile gösterilir:
pointer_type
: ...
| funcptr_type
;
funcptr_type
: 'delegate' '*' calling_convention_specifier? '<' funcptr_parameter_list funcptr_return_type '>'
;
calling_convention_specifier
: 'managed'
| 'unmanaged' ('[' unmanaged_calling_convention ']')?
;
unmanaged_calling_convention
: 'Cdecl'
| 'Stdcall'
| 'Thiscall'
| 'Fastcall'
| identifier (',' identifier)*
;
funptr_parameter_list
: (funcptr_parameter ',')*
;
funcptr_parameter
: funcptr_parameter_modifier? type
;
funcptr_return_type
: funcptr_return_modifier? return_type
;
funcptr_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
funcptr_return_modifier
: 'ref'
| 'ref readonly'
;
calling_convention_specifier sağlanmadıysa, varsayılan değer managedolur.
delegate int Func1(string s);
delegate Func1 Func2(Func1 f);
// Function pointer equivalent without calling convention
delegate*<string, int>;
delegate*<delegate*<string, int>, delegate*<string, int>>;
// Function pointer equivalent with calling convention
delegate* managed<string, int>;
delegate*<delegate* managed<string, int>, delegate*<string, int>>;
İşlev işaretçisi dönüştürmeleri
Güvenli olmayan bir bağlamda, kullanılabilir örtük dönüştürmeler (Örtük dönüştürmeler) kümesi aşağıdaki örtük işaretçi dönüştürmelerini içerecek şekilde genişletilir:
- Var olan dönüştürmeler - (§23.5)
-
funcptr_type
F0'den funcptr_typeF1'e, aşağıdakilerin tümü doğru olduğunda:-
F0veF1aynı sayıda parametreye sahiptir veD0n'teki her bir parametreF0,ref'deki ilgili parametreoutile aynıin,D1nveyaF1değiştiricilerine sahiptir. - Her değer parametresi için (
ref,outveyaindeğiştiricisi olmayan bir parametre),F0içindeki parametre türündenF1'deki ilgili parametre türüne kimlik dönüştürme, örtük başvuru dönüştürme veya örtük işaretçi dönüştürmesi vardır. - her
ref,outveyainparametresi için,F0içindeki parametre türü,F1içindeki ilgili parametre türüyle aynıdır. - Dönüş türü değere göreyse (
refveyaref readonlyolmadan),F1dönüş türündenF0dönüş türüne bir kimlik, örtük başvuru veya örtük işaretçi dönüştürmesi vardır. - Dönüş türü referans olarak veriliyorsa (
refveyaref readonly),ref'ün dönüş türü veF1değiştiricileri,ref'in dönüş türü veF0değiştiricileriyle aynıdır. -
F0çağırma kuralı,F1çağırma kuralıyla aynıdır.
-
Hedef yöntemlerin adresine izin ver
Yöntem gruplarına artık bir ifadenin adresi için bağımsız değişken olarak izin verilir. Böyle bir ifadenin türü, hedef yöntemin eşdeğer imzasını ve yönetilen çağırma kuralını içeren bir delegate* olacaktır:
unsafe class Util {
public static void Log() { }
void Use() {
delegate*<void> ptr1 = &Util.Log;
// Error: type "delegate*<void>" not compatible with "delegate*<int>";
delegate*<int> ptr2 = &Util.Log;
}
}
Güvenli olmayan bir bağlamda M yöntemi, aşağıdakilerin tümü doğruysa F işlev işaretçisi türüyle uyumludur:
-
MveFaynı sayıda parametreye sahiptir veM'daki her parametre,ref'de karşılık gelen parametreyle aynıout,inveyaFdeğiştiricilere sahiptir. - Her değer parametresi için (
ref,outveyaindeğiştiricisi olmayan bir parametre),Miçindeki parametre türündenF'deki ilgili parametre türüne kimlik dönüştürme, örtük başvuru dönüştürme veya örtük işaretçi dönüştürmesi vardır. - her
ref,outveyainparametresi için,Miçindeki parametre türü,Fiçindeki ilgili parametre türüyle aynıdır. - Dönüş türü değere göreyse (
refveyaref readonlyolmadan),Fdönüş türündenMdönüş türüne bir kimlik, örtük başvuru veya örtük işaretçi dönüştürmesi vardır. - Dönüş türü referans olarak veriliyorsa (
refveyaref readonly),ref'ün dönüş türü veFdeğiştiricileri,ref'in dönüş türü veMdeğiştiricileriyle aynıdır. -
Mçağırma kuralı,Fçağırma kuralıyla aynıdır. Bu, hem çağırma kuralı bitini hem de yönetilmeyen tanımlayıcıda belirtilen çağırma kuralı bayraklarını içerir. -
Mstatik bir yöntemdir.
Güvenli olmayan bir bağlamda, E bir yöntem grubunu hedefleyen bir adres ifadesinden F uyumlu bir işlev işaretçisi türüne örtük bir dönüştürme vardır, eğer E, aşağıda açıklandığı gibi, Fparametre türleri ve değiştiricileri kullanılarak oluşturulan bir bağımsız değişken listesine normal biçiminde uygulanabilir en az bir yöntem içeriyorsa.
- Aşağıdaki değişikliklerle form
Mbir yöntem çağrısına karşılık gelen tek bir yöntemE(A)seçilir:- Bağımsız değişkenler listesi
A, her biri değişken olarak sınıflandırılmış ifadelerden oluşan bir listedir veref'ya karşılık gelenouttürü ve değiştiricisi (in, veyaF) belirtilmiştir. - Aday yöntemleri yalnızca normal biçimlerinde geçerli olan yöntemlerdir, genişletilmiş biçimlerinde geçerli olan yöntemler değildir.
- Aday yöntemleri yalnızca statik olan yöntemlerdir.
- Bağımsız değişkenler listesi
- Aşırı yükleme çözümleme algoritması bir hata üretirse, derleme zamanı hatası oluşur. Aksi takdirde algoritma,
Mile aynı sayıda parametreye sahipFtek bir en iyi yöntem üretir ve dönüştürmenin mevcut olduğu kabul edilir. - Seçilen yöntem
MFişlev işaretçisi türüyle uyumlu olmalıdır (yukarıda tanımlandığı gibi). Aksi takdirde derleme zamanı hatası oluşur. - Dönüştürmenin sonucu,
Ftüründe bir işlev işaretçisidir.
Bu, geliştiricilerin adres-of operatörü ile birlikte çalışması için aşırı yük çözümleme kurallarına güvenebileceği anlamına gelir.
unsafe class Util {
public static void Log() { }
public static void Log(string p1) { }
public static void Log(int i) { }
void Use() {
delegate*<void> a1 = &Log; // Log()
delegate*<int, void> a2 = &Log; // Log(int i)
// Error: ambiguous conversion from method group Log to "void*"
void* v = &Log;
}
}
İşlecin adresi, ldftn yönergesi kullanılarak uygulanır.
Bu özelliğin kısıtlamaları:
- Yalnızca
staticolarak işaretlenmiş yöntemler için geçerlidir. -
staticolmayan yerel işlevler&içinde kullanılamaz. Bu yöntemlerin uygulama ayrıntıları, dil tarafından kasıtlı olarak belirtilmez. Bu, bunların statik mi yoksa örnekleme mi olduklarını veya tam olarak hangi imzayla yayımlandıklarını da içerir.
Fonksiyon İşaretçisi Türleri Üzerinde İşleçler
İfadelerdeki güvenli olmayan kod bölümü şu şekilde değiştirilir:
Güvenli olmayan bir bağlamda, _funcptr_type_s olmayan tüm _pointer_type_s üzerinde işlem yapmak için çeşitli yapılar kullanılabilir.
*işleci işaretçi dolaylı işlemi gerçekleştirmek için kullanılabilir (§23.6.2).->işleci, bir yapının üyesine bir işaretçi aracılığıyla (§23.6.3) erişmek için kullanılabilir.[]işleci bir işaretçiyi dizine eklemek için kullanılabilir (§23.6.4).&işleci bir değişkenin adresini almak için kullanılabilir (§23.6.5).++ve--işleçleri işaretçileri artırmak ve azaltmak için kullanılabilir (§23.6.6).+ve-işleçleri işaretçi aritmetiği gerçekleştirmek için kullanılabilir (§23.6.7).- İşaretçileri karşılaştırmak için
==,!=,<,>,<=ve=>işleçleri kullanılabilir (§23.6.8).stackallocişleci çağrı yığınından bellek ayırmak için kullanılabilir (§23.8).fixeddeyimi, adresinin alınabilmesi için değişkeni geçici olarak düzeltmek için kullanılabilir (§23.7).Güvenli olmayan bir bağlamda, tüm _funcptr_type_s üzerinde birkaç yapı kullanılabilir:
&işleci statik yöntemlerin adresini almak için kullanılabilir (Hedef metotların adresine izin ver)- İşaretçileri karşılaştırmak için
==,!=,<,>,<=ve=>işleçleri kullanılabilir (§23.6.8).
Ayrıca, Pointers in expressions'da, Pointer comparison ve The sizeof operatorharicindeki tüm bölümlerde işlev işaretçisi türlerini yasakladık.
Daha iyi işlev üyesi
§12.6.4.3 Daha iyi işlev üyesi aşağıdaki satırı içerecek şekilde değiştirilecektir:
delegate*void*daha özeldir
Bu, void* ve delegate* üzerinde aşırı yükleme yapmanın ve hala adres-of operatörünü mantıklı bir şekilde kullanmaya devam etmenin mümkün olduğu anlamına gelir.
Tür Çıkarımı
Güvenli olmayan kodda tür çıkarım algoritmalarında aşağıdaki değişiklikler yapılır:
Giriş türleri
Aşağıdakiler eklenir:
Ebir adresli yöntem grubuysa veTbir işlev işaretçisi türüyse,T'nin tüm parametre türleri,Etüründe,Tiçin giriş türleridir.
Çıkış türleri
Aşağıdakiler eklenir:
Ebir yöntem grubunun adresiyse veTbir işlev işaretçisi türüyse,T'nin dönüş türüEtüründe birTçıkış türüdür.
Çıkış türü çıkarımları
2 ile 3 numaralı maddeler arasına aşağıdaki madde eklenir:
E, bir yöntem grubunun adresiyse veT, parametre türleriT1...Tkve dönüş türüTbolan bir işlev gösterici türüyse veEtürleriyleT1..Tk'ün aşırı yükleme çözümleme süreci,Udönüş türüne sahip tek bir yöntemi sağlıyorsa, 'danU'a birTbyapılır.
İfadedeki daha iyi dönüşüm
Madde işareti 2'ye vaka olarak aşağıdaki alt madde işareti eklenir:
Vdelegate*<V2..Vk, V1>bir işlev işaretçisi türüdür,Udelegate*<U2..Uk, U1>bir işlev işaretçisi türüdür veVçağırma kuralıUile aynıdır veVibaşvuru durumuUiile aynıdır.
Alt sınır tabanlı çıkarımlar
Madde işareti 3'e aşağıdaki durum eklenir:
V,delegate*<V2..Vk, V1>bir işlev işaretçisi türüdür vedelegate*<U2..Uk, U1>Uile özdeş vedelegate*<U2..Uk, U1>çağırma kuralıVile aynıdır veUViile aynıdırUiişlev işaretçisi türü vardır.
Ui ile Vi arasındaki çıkarsamaların ilk madde işareti şu şekilde değiştirilmiştir:
Ubir işlev işaretçisi türü değilse veUi'in başvuru türü olduğu bilinmiyorsa veyaUbir işlev işaretçisi türüyse veUi'ün işlev işaretçisi türü ya da başvuru türü olduğu bilinmiyorsa, kesin çıkarım yapılır
Ardından, 3. çıkarım madde işaretinden sonra UiVieklendi:
- Aksi takdirde,
Vdelegate*<V2..Vk, V1>ise çıkarsama,delegate*<V2..Vk, V1>'nin i. parametresine bağlıdır.
- V1 ise:
- Eğer dönüş değer ile yapılırsa, alt sınır çıkarımı yapılır.
- Referansla dönüş yapılıyorsa, tam çıkarım yapılır.
- "Eğer V2..Vk ise:"
- Parametre değere göre veriliyorsa, üst sınır çıkarımı yapılır.
- Parametre başvuru olarak belirtilmişse, tam çıkarım yapılır.
Üst sınır tahminleri
Madde işareti 2’ye aşağıdaki vaka eklenir:
Uvedelegate*<U2..Uk, U1>, sırasıylaVvedelegate*<V2..Vk, V1>ile aynı olan işlev işaretçisi türleridir,U'ün çağırma kuralıVile aynıdır veUi'nın başvuru durumuViile aynıdır.
Ui ile Vi arasındaki çıkarsamaların ilk madde işareti şu şekilde değiştirilmiştir:
Ubir işlev işaretçisi türü değilse veUi'in başvuru türü olduğu bilinmiyorsa veyaUbir işlev işaretçisi türüyse veUi'ün işlev işaretçisi türü ya da başvuru türü olduğu bilinmiyorsa, kesin çıkarım yapılır
Ardından, Ui'den Vi'a çıkarımın 3. madde işaretinden sonra eklenir:
- Aksi takdirde,
Udelegate*<U2..Uk, U1>ise çıkarsama,delegate*<U2..Uk, U1>'nin i. parametresine bağlıdır.
- U1 ise:
- Dönüş değere göreyse, bir üst sınır çıkarımı yapılır.
- Referansla dönüş yapılıyorsa, tam çıkarım yapılır.
- Eğer U2..Uk:
- Parametre değere göreyse, alt sınır çıkarım yapılır.
- Parametre başvuru olarak belirtilmişse, tam çıkarım yapılır.
in, outve ref readonly parametrelerinin ve dönüş türlerinin meta veri gösterimi
İşlev işaretçisi imzalarının parametre bayrakları konumu yoktur, bu nedenle parametrelerin ve dönüş türünün modreqs kullanarak in, outveya ref readonly olup olmadığını kodlamamız gerekir.
in
System.Runtime.InteropServices.InAttribute'ı, bir parametre veya dönüş türündeki bağlaç tanımlayıcısına modreq olarak uygulayarak şu anlama gelmesi için yeniden kullanırız:
- Bir parametre başvuru belirticisine uygulanırsa, bu parametre
inolarak değerlendirilir. - Dönüş türü "ref" belirticisine uygulanırsa, dönüş türü
ref readonlyolarak değerlendirilir.
out
Parametre türündeki ref belirticisine System.Runtime.InteropServices.OutAttribute olarak uygulanan modreq'ı, parametrenin bir out parametresi olduğunu belirtmek için kullanırız.
Hata
- bir dönüş türüne modreq olarak
OutAttributeuygulamak bir hatadır. - Parametre türüne modreq olarak hem
InAttributehem deOutAttributeuygulanması bir hatadır. - Modopt aracılığıyla herhangi biri belirtilirse, yoksayılır.
Çağırma Kurallarının Meta Veri Gösterimi
Çağırma kuralları, meta verilerdeki yöntem imzasında, imzanın içindeki CallKind bayrağı ve imzanın başındaki sıfır veya daha fazla modopt'in birleşimiyle kodlanır. ECMA-335 şu anda CallKind bayrağında aşağıdaki öğeleri bildirir:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
Bunlardan, C# içindeki işlev işaretçileri varargsdışında tümünü destekleyecektir.
Ayrıca, çalışma süreci (ve en sonunda 335), yeni platformlara yeni bir CallKind eklemek üzere güncelleştirilecektir. Bu, şu anda resmi bir adı yoktur, ancak bu belge yeni genişletilebilir çağrı kuralı biçimi için yer tutucu olarak unmanaged ext kullanacaktır.
modoptolmadan unmanaged ext, köşeli ayraçlar olmadan unmanaged platform varsayılan çağırma kuralıdır.
calling_convention_specifier'ı CallKind'e eşleme işlemi
atlanmış veya calling_convention_specifierolarak belirtilen bir managed, defaultCallKindile eşler. Herhangi bir yöntemin CallKindile ilişkilendirilmediğinde varsayılan durumu UnmanagedCallersOnly'dur.
C#, ECMA 335'te tanımlı ve belirli mevcut yönetilmeyen CallKind'lara eşlenen 4 özel tanımlayıcıyı tanır. Bu eşlemenin gerçekleşmesi için, bu tanımlayıcıların başka tanımlayıcı olmadan kendi başlarına belirtilmesi gerekir ve bu gereksinim unmanaged_calling_conventionbelirtimine kodlanır. Bu tanımlayıcılar sırasıyla Cdecl, Thiscall, Stdcallve Fastcallkarşılık gelen unmanaged cdecl, unmanaged thiscall, unmanaged stdcallve unmanaged fastcall' dır. Birden fazla identifer belirtilirse veya tek identifier özel olarak tanınan tanımlayıcılardan değilse, tanımlayıcıda aşağıdaki kurallarla özel ad araması yaparız:
-
identifierdizisiniCallConv'nın önüne ekliyoruz - Yalnızca
System.Runtime.CompilerServicesad alanında tanımlanan türlere bakıyoruz. - Yalnızca uygulamanın
System.Objecttanımlayan ve hiçbir bağımlılığı olmayan çekirdek kitaplığında tanımlanan türlere bakıyoruz. - Yalnızca genel türlere bakıyoruz.
Arama, bir identifierbelirtilen tüm unmanaged_calling_convention'lerde başarılı olursa, CallKind'yi unmanaged extolarak kodlarız ve çözümlenen türlerin her birini işlev işaretçisi imzasının başındaki modopt'ler kümesinde kodlarız. Not olarak, bu kurallar kullanıcıların bu identifier'ı CallConvile ön ek olarak kullanamayacağı, çünkü bunun CallConvCallConvVectorCallaramasıyla sonuçlanacağı anlamına gelir.
Meta verilerini yorumlarken ilk olarak CallKind'a bakarız.
unmanaged extdışında bir şey durumunda, çağırma kurallarını belirlerken dönüş türündeki tüm modopt'leri yoksayarız ve yalnızca CallKindkullanırız.
CallKind
unmanaged extise, aşağıdaki gereksinimleri karşılayan tüm türlerin birleşimini alarak işlev işaretçisi türünün başında modopt'lara bakarız:
- Tanım, diğer kitaplıklara referans vermeyen ve
System.Object'ı tanımlayan bir kütüphane olan çekirdek kütüphanede tanımlanır. - Tür,
System.Runtime.CompilerServicesad alanında tanımlanır. - Tür,
CallConvön ekiyle başlar. - Türü geneldir.
Bunlar, kaynakta işlev işaretçisi türü tanımlarken bir identifier içinde bir unmanaged_calling_conventionüzerinde yapılan aramada bulunması gereken türleri temsil eder.
Hedef çalışma zamanı özelliği desteklemiyorsa, CallKindunmanaged ext ile işlev işaretçisi kullanmaya çalışmak bir hatadır. Bu, System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind sabitinin varlığı aranarak belirlenir. Bu sabit varsa, çalışma zamanının özelliği desteklediği kabul edilir.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute, bir yöntemin belirli bir çağırma kuralıyla çağrılması gerektiğini belirtmek için CLR tarafından kullanılan bir özniteliktir. Bu nedenle özniteliğiyle çalışmak için aşağıdaki desteği sunacağız:
- C# öğesinden bu öznitelikle açıklama eklenen bir yöntemi doğrudan çağırmak bir hatadır. Kullanıcıların yöntemine bir işlev işaretçisi alması ve ardından bu işaretçiyi çağırması gerekir.
- Özniteliği sıradan bir statik yöntem veya sıradan statik yerel işlev dışında herhangi bir şeye uygulamak bir hatadır. C# derleyicisi bu öznitelikle meta verilerden içeri aktarılan statik olmayan veya statik sıradan olmayan yöntemleri dil tarafından desteklenmeyen olarak işaretler.
- özniteliğiyle işaretlenmiş bir yöntemin
unmanaged_typeolmayan bir parametreye veya dönüş türüne sahip olması hatadır. - özniteliğiyle işaretlenmiş bir yöntemin tür parametrelerine sahip olması, bu tür parametreleri
unmanagedile kısıtlanmış olsa bile bir hatadır. - Genel türdeki bir yöntemin özniteliğiyle işaretlenmesi bir hatadır.
- Özniteliğiyle işaretlenmiş bir yöntemi temsilci türüne dönüştürmek bir hatadır.
-
UnmanagedCallersOnly.CallConvsiçin meta verilerdemodoptçağırma kuralı gereksinimlerini karşılamayan herhangi bir tür belirtmek bir hatadır.
Geçerli bir UnmanagedCallersOnly özniteliğiyle işaretlenmiş bir yöntemin çağırma kuralını belirlerken, derleyici CallConvs özelliğinde belirtilen türler üzerinde aşağıdaki denetimleri gerçekleştirerek, çağırma kuralını belirlemek için kullanılması gereken etkili CallKind ve modopt'leri belirler:
- Hiçbir tür belirtilmezse,
CallKindunmanaged extolarak değerlendirilir ve işlev işaretçisi türünün başında çağırma kuralımodoptyoktur. - Belirtilen bir tür varsa ve bu tür
CallConvCdecl,CallConvThiscall,CallConvStdcallveyaCallConvFastcallolarak adlandırılırsa,CallKindsırasıylaunmanaged cdecl,unmanaged thiscall,unmanaged stdcallveyaunmanaged fastcallolarak değerlendirilir ve işlev işaretçisi türünün başında hiçbir çağırma kuralımodoptyoktur. - Birden çok tür belirtilirse veya tek tür yukarıdaki özel olarak çağrılan türlerden biri olarak adlandırılmazsa,
CallKindunmanaged extolarak değerlendirilir ve belirtilen türlerin birleşimi işlev işaretçisi türünün başındamodoptolarak değerlendirilir.
Derleyici daha sonra bu etkili CallKind ve modopt koleksiyonuna bakar ve işlev işaretçisi türünün son çağrı kuralını belirlemek için normal meta veri kurallarını kullanır.
Açık Sorular
unmanaged ext için çalışma zamanı desteğini algılama
https://github.com/dotnet/runtime/issues/38135 bu bayrağı eklemeyi izler. Gözden geçirmeden gelen geri bildirime bağlı olarak, ya sorunda belirtilen özelliği kullanacak ya da çalışma zamanlarının UnmanagedCallersOnlyAttribute'i destekleyip desteklemediğini belirlemek için bayrak olarak unmanaged ext'ın varlığını kullanacağız.
Hususlar
Örnek yöntemlerine izin ver
Teklif, EXPLICITTHIS CLI çağırma kuralından (C# kodunda instance adlı) yararlanarak örnek yöntemlerini destekleyecek şekilde genişletilebilir. BU CLI işlev işaretçileri biçimi, this parametresini işlev işaretçisi söz diziminin açık bir ilk parametresi olarak koyar.
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
Bu mantıklıdır ancak teklifi biraz daha karmaşık hale getirir. Çağrı kuralı instance ve managed nedeniyle farklı olan işlev işaretçileri, her ne kadar her iki durumda da aynı C# imzasına sahip yönetilen yöntemleri çağırmak için kullanılsa da uyumsuz olacaktır. Ayrıca, bunun değerli olacağı düşünülen her durumda basit bir alternatif vardı: bir static yerel işlevi kullanmak.
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
Deklarasyonda güvensizi zorunlu tutmayın
Bir unsafeher kullanımında delegate* gerektirmek yerine, yalnızca bir yöntem grubunun delegate*'ye dönüştürüldüğü noktada gerektirilsin. Çekirdek güvenlik sorunlarının devreye girdiği yer burasıdır (değer canlıyken içeren derlemenin kaldırılamayacağını bilerek). Diğer konumlarda unsafe'un zorunlu olarak tutulması aşırı olarak görülebilir.
Tasarım başlangıçta bu şekilde tasarlanmıştır. Ancak ortaya çıkan dil kuralları çok garip geldi. Bunun bir işaretçi değeri olduğu ve unsafe anahtar sözcüğü olmadan bile ortaya çıktığı gerçeğini gizlemek mümkün değildir. Örneğin object dönüştürmeye izin verilmiyor, classüyesi olamaz. C# tasarımı, tüm işaretçi kullanımları için unsafe gerektirir ve bu nedenle bu tasarım bunu izler.
Geliştiriciler, güvenli sarmalayıcıyı delegate* değerlerin üzerinde bugün normal işaretçi türlerinde olduğu gibi sunabilecek. Göz önünde bulundur:
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
Temsilcileri kullanma
Yeni bir söz dizimi öğesi olan delegate*'ü kullanmak yerine, var olan delegate türlerini, türü izleyen bir * ile kullanın.
Func<object, object, bool>* ptr = &object.ReferenceEquals;
Çağırma kuralının işlenmesi, delegate değeri belirten bir öznitelikle CallingConvention türlerine ek açıklama eklenerek yapılabilir. Özniteliğin bulunmaması, yönetilen çağrı kuralını temsil eder.
Bunu IL'de kodlamak sorunludur. Temel alınan değerin bir işaretçi olarak temsil edilmesi gerekir, aynı zamanda aşağıdakilere de uymalıdır:
- Farklı işlev işaretçisi türlerine sahip aşırı yüklemelere izin vermek için benzersiz bir türe sahip olun.
- Bütünleştirilmiş kod sınırları boyunca OHI amaçlarına eşdeğer olmalıdır.
Son nokta özellikle sorunlu. Bu, Func<int>* kullanan her derlemenin, Func<int>* bir derlemede tanımlansa bile meta verilerde eşdeğer bir tür kodlaması gerektiği anlamına gelir ancak bunu denetlemez.
Ayrıca, mscorlib olmayan bir derlemede System.Func<T> adıyla tanımlanan diğer tüm türlerin mscorlib'de tanımlanan sürümden farklı olması gerekir.
Keşfedilen seçeneklerden biri, mod_req(Func<int>) void*gibi bir işaretçi yaymaydı. Bir mod_req'ın bir TypeSpec'e bağlanamaması durumu, genel örneklemeleri hedef almadığı için işe yaramaz.
Adlandırılmış işlev işaretçileri
İşlev işaretçisi söz dizimi, özellikle iç içe yerleştirilmiş işlev işaretçileri gibi karmaşık durumlarda hantal olabilir. Geliştiricilerin her seferinde imzayı yazmak zorunda kalması yerine, dilin delegateile yapıldığı gibi işlev işaretçileri için adlandırılmış bildirimlere izin verebilmesi.
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
Buradaki sorunun bir kısmı, temel alınan CLI temel öğesinin adları olmamasıdır, bu nedenle bu yalnızca bir C# buluşu olur ve etkinleştirmek için biraz meta veri çalışması gerektirir. Bu yapılabilir bir işlemdir ancak işle ilgili önemli bir konudur. C# dilinin, yalnızca bu adlar için tür def tablosuna eşlik eden bir şeyinin olması gerektiğini gerektirir.
Ayrıca, isimlendirilmiş fonksiyon işaretçileri için argümanlar incelendiğinde, bunların birçok diğer senaryoya aynı derecede etkili bir şekilde uygulanabileceğini bulduk. Örneğin, tüm durumlarda tam imzayı yazma gereksinimini azaltmak için adlı tupları tanımlamak da kullanışlı olabilir.
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
Tartışmadan sonra delegate* türlerinin adlandırılmış bildirimine izin vermemeye karar verdik. Müşteri kullanımı geri bildirimlerine dayalı olarak buna önemli bir ihtiyaç olduğunu tespit ettiğimizde işlev işaretçileri, tanımlama grupları, genel değerler vb. için çalışan bir adlandırma çözümünü araştıracağız. Bu, büyük olasılıkla dildeki tam typedef desteği gibi diğer önerilere benzer olacaktır.
Gelecekte Dikkat Edilmesi Gerekenler
statik temsilciler
Yalnızca delegate örneklerinin, bellek ayırmaya gerek kalmadan ve performansa duyarlı senaryolarda daha iyi performans göstermesinin avantajıdır.
Fonksiyon işaretleyicisi özelliği uygulanırsa, static delegate teklifi büyük olasılıkla kapanır. Bu özelliğin önerilen avantajı, bellek ayırımı gerektirmeyen yapısıdır. Ancak yakın zamanda yapılan araştırmalar, derleme bileşenlerinin kaldırılması nedeniyle bunun mümkün olmadığını buldu. Derlemenin kaldırılmasını önlemek için static delegate'ın başvurduğu yönteme güçlü bir işaretçi olmalıdır.
Her static delegate örneğini korumak için, teklifin hedeflerine ters düşen yeni bir tanıtıcı tahsis edilmesi gerekir. Ayırmanın çağrı sitesi başına tek bir ayırmaya amorti edilebileceği ancak biraz karmaşık olduğu ve takasa değer görünmediği bazı tasarımlar vardı.
Bu, geliştiricilerin temelde aşağıdaki ticari dezavantajlar arasında karar vermek zorunda olduğu anlamına gelir:
- Montajın boşaltılması karşısında güvenlik: Bu, kaynak tahsisi gerektirir ve bu nedenle
delegateyeterli bir seçenektir. - Montaj boşaltması karşısında güvenlik yok:
delegate*kullanın. Bu, kodun geri kalanında birstructbağlamı dışında kullanıma izin vermek için birunsafeiçinde sarmalanabilir.
C# feature specifications