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 dil tasarım toplantısı (LDM) notlarındakaydedilir.
Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek için
Şampiyonluk sorunları: https://github.com/dotnet/csharplang/issues/1147, https://github.com/dotnet/csharplang/issues/6476
Özet
Bu teklif, struct
performans geliştirmeleri için birkaç farklı teklifin bir toplamıdır: ref
alanları ve yaşam süresi varsayılanlarını geçersiz kılma olanağı. Amaç, alt düzey struct
iyileştirmeleri için çeşitli teklifleri dikkate alarak genel bir özellik kümesi oluşturan bir tasarım yapmaktır.
Not: Bu belirtimin önceki sürümleri, Span güvenliği özellik belirtiminde kullanıma sunulan "ref-safe-to-escape" ve "safe-to-escape" terimlerini kullanıyordu. ECMA standart komitesi adlarını sırasıyla "ref-safe-context" ve "safe-context"olarak değiştirdi. Güvenli bağlamın değerleri tutarlı bir şekilde "declaration-block", "function-member" ve "caller-context" kullanacak şekilde geliştirilmiştir. Bölümcükler, bu terimler için farklı ifadeler kullanmış ve ayrıca "çağıran bağlam" için "dönüş için güvenli" ifadesini eş anlamlı olarak kullanmıştı. Bu belirtim, C# 7.3 standardındaki terimleri kullanacak şekilde güncelleştirildi.
Bu belgede özetlenen özelliklerin tümü C# 11'de uygulanmadı. C# 11 şunları içerir:
-
ref
alanları vescoped
[UnscopedRef]
Bu özellikler, gelecekteki bir C# sürümü için açık teklifler olmaya devam eder:
-
ref
alanlarındanref struct
alanlarına - Gün batımı kısıtlanmış türleri
Motivasyon
C# uygulamasının önceki sürümlerinde dile bir dizi düşük düzey performans özelliği eklendi: ref
döndürür, ref struct
, işlev işaretçileri vb. ... Bu, .NET geliştiricilerinin yüksek performanslı kod yazmasına olanak tanırken, tür ve bellek güvenliği için C# dil kurallarından yararlanmaya devam etti. Ayrıca Span<T>
gibi .NET kitaplıklarında temel performans türlerinin oluşturulmasına da izin verdi.
.NET ekosistemindeki bu özellikler yaygınlaştıkça, hem iç hem de dış geliştiriciler ekosistemde kalan pürüz noktaları hakkında bize bilgi sağlamaktadır. İşlerini yapmak için hala unsafe
koduna düşmeleri gereken yerler veya çalışma zamanında Span<T>
gibi özel durum tiplerini gerektiren yerler.
Bugün Span<T>
, çalışma zamanının etkili bir şekilde internal
alanı olarak davrandığı ByReference<T>
türü ref
kullanılarak gerçekleştirilir. Bu, ref
alanlarının avantajını sağlar, ancak dilin diğer ref
kullanımlarında olduğu gibi bunun için güvenlik doğrulaması sağlamaması dezavantajı da sağlar.
internal
olduğu için sadece dotnet/runtime bu türü kullanabilir, bu nedenle üçüncü taraflar ref
alanlarına göre kendi temel öğelerini tasarlayamaz. Bu çalışma için
Bu teklif, mevcut düşük düzey özelliklerimizi kullanarak bu sorunları çözmeyi planlıyor. Özellikle şu amaçlara hedefleniyor:
-
ref struct
türlerininref
alanları bildirmesine izin verin. - Çalışma zamanının C# türü sistemini kullanarak
Span<T>
tam olarak tanımlamasına veByReference<T>
gibi özel durum türünü kaldırmasına izin verme -
struct
türlerinin alanlarınaref
döndürmesine izin verin. - Çalışma zamanının, yaşam süresi varsayılanlarının sınırlamalarından kaynaklanan
unsafe
kullanımlarını kaldırmasına izin ver -
fixed
yönetilen ve yönetilmeyen türler için güvenlistruct
arabelleklerinin bildirilmesine izin ver
Ayrıntılı Tasarım
ref struct
güvenlik kuralları, span güvenlik belgesinde önceki koşullar kullanılarak tanımlanır. Bu kurallar §9.7.2 C# 7 standardına eklenmiştir ve §16.4.12. Bu belgede, bu teklif sonucunda gerekli değişiklikler açıklanır. Onaylanan bir özellik olarak kabul edildikten sonra bu değişiklikler bu belgeye eklenir.
Bu tasarım tamamlandıktan sonra Span<T>
tanımımız aşağıdaki gibi olacaktır:
readonly ref struct Span<T>
{
readonly ref T _field;
readonly int _length;
// This constructor does not exist today but will be added as a part
// of changing Span<T> to have ref fields. It is a convenient, and
// safe, way to create a length one span over a stack value that today
// requires unsafe code.
public Span(ref T value)
{
_field = ref value;
_length = 1;
}
}
Başvuru alanlarını ve kapsamı belirlenmiş alanları sağlayın
Dil, geliştiricilerin bir ref
içinde ref struct
alanları bildirmesine olanak sağlar. Bu, örneğin büyük değiştirilebilir struct
örneklerini kapsüllerken veya çalışma zamanının yanı sıra kitaplıklarda Span<T>
gibi yüksek performanslı türler tanımlarken yararlı olabilir.
ref struct S
{
public ref int Value;
}
ref
imzası kullanılarak meta veriler içine bir ELEMENT_TYPE_BYREF
alanı gönderilir. Bu, yerel ref
veya ref
bağımsız değişkenlerini üretmekten farklı değildir. Örneğin ref int _field
ELEMENT_TYPE_BYREF ELEMENT_TYPE_I4
olarak gönderilir. Bu, bu girişe izin vermek için ECMA335'i güncellememizi gerektirir, ancak bu işlem oldukça basit olmalıdır.
Geliştiriciler, ref struct
ifadesini kullanarak bir ref
alanıyla default
başlatmaya devam edebilir ve bu durumda bildirilen tüm ref
alanları null
değerine sahip olur. Bu tür alanları kullanmaya yönelik her girişim bir NullReferenceException
atılmasıyla sonuçlanır.
ref struct S
{
public ref int Value;
}
S local = default;
local.Value.ToString(); // throws NullReferenceException
tr-TR: C# dili, bir ref
'ın null
olamayacağını iddia etse de, bu çalışma zamanı düzeyinde yasaldır ve iyi tanımlanmış semantiklere sahiptir. türlerine ref
alanlar, bunun yerine çalışma zamanı yardımcıları kullanılarak null olmayan olarak doğrulanmalı ve başlatılmamış bir struct
yanlış kullanıldığında hata fırlatılmalıdır.
ref struct S1
{
private ref int Value;
public int GetValue()
{
if (System.Runtime.CompilerServices.Unsafe.IsNullRef(ref Value))
{
throw new InvalidOperationException(...);
}
return Value;
}
}
ref
alanı, readonly
değiştiricilerle aşağıdaki yollarla birleştirilebilir:
-
readonly ref
: Bu, oluşturucu veyainit
yöntemleri dışında yeniden atanamayan bir başvuru alanıdır. Bu bağlamların dışında olsa da değer atanabilir -
ref readonly
: Bu, referans yeniden atanabilir, ancak herhangi bir noktada değer atanamaz bir alandır. Bu şekilde, birin
parametresi birref
alanına referans olarak yeniden atanabilir. -
readonly ref readonly
:ref readonly
vereadonly ref
birleşimi.
ref struct ReadOnlyExample
{
ref readonly int Field1;
readonly ref int Field2;
readonly ref readonly int Field3;
void Uses(int[] array)
{
Field1 = ref array[0]; // Okay
Field1 = array[0]; // Error: can't assign ref readonly value (value is readonly)
Field2 = ref array[0]; // Error: can't repoint readonly ref
Field2 = array[0]; // Okay
Field3 = ref array[0]; // Error: can't repoint readonly ref
Field3 = array[0]; // Error: can't assign ref readonly value (value is readonly)
}
}
readonly ref struct
için ref
alanlarının readonly ref
bildirilmesi gerekecektir.
readonly ref readonly
'nin bildirilmesi gerektiğine dair bir zorunluluk yoktur. Bu, bir readonly struct
'a bu tür bir alan aracılığıyla dolaylı mutasyonlar yapabilmeye imkan tanır, ancak bu, bugün referans türüne işaret eden bir readonly
alanından farklı değildir (daha fazla ayrıntı).
readonly ref
, diğer tüm alanlarda olduğu gibi initonly
bayrağı kullanılarak meta veriye gönderilir. bir ref readonly
alanı System.Runtime.CompilerServices.IsReadOnlyAttribute
ile ilişkilendirilecektir. Her iki öğeyle de bir readonly ref readonly
gönderilir.
Bu özellik için çalışma zamanı desteği ve ECMA belirtiminde değişiklikler yapılması gerekir. Bu nedenle bunlar yalnızca ilgili özellik bayrağı corelib içinde ayarlandığında etkinleştirilir. Tam API'yi izleme sorunu burada https://github.com/dotnet/runtime/issues/64165
ref
alanlara izin vermek için gerekli olan güvenli bağlam kurallarımızda yapılan değişiklikler kümesi küçüktür ve hedeflenmiştir. Kurallar zaten API'lerden var olan ve tüketilen ref
alanlarını hesaba alır. Değişikliklerin yalnızca iki yönüne odaklanması gerekir: bunların nasıl oluşturulduğu ve nasıl yeniden atandıkları.
İlk olarak, alanlar için ref-safe-context değerlerini oluşturan kuralların ref
alanları için aşağıdaki gibi güncelleştirilmiş olması gerekir:
ref e.F
ref-safe-context şeklinde bir ifade:
F
birref
alanıysa, ref-safe-context, 'nıne
'dur.e
bir başvuru türündeyse, ref-safe-context, çağıran bağlamı'ye sahiptir- Aksi takdirde, ref-safe-context, 'ün
e
'ünden alınır.
Kurallar her zaman bir ref
içinde ref struct
durumunu hesaba katmış olsa da, bu bir kural değişikliğini temsil etmez. Gerçekte, ref
'deki Span<T>
durumunun her zaman çalıştığı şekil budur ve tüketim kuralları bunu doğru şekilde hesaba katmıştır. Buradaki değişiklik, geliştiricilerin ref
alanlara doğrudan erişebilmesini ve bunu Span<T>
'a örtük olarak uygulanan mevcut kurallarla gerçekleştirebilmelerini sağlamaktır.
Ancak bu, ref
alanların bir ref
'nın ref struct
olarak döndürülebileceği, ancak normal alanların döndürülemeyeceği anlamına gelir.
ref struct RS
{
ref int _refField;
int _field;
// Okay: this falls into bullet one above.
public ref int Prop1 => ref _refField;
// Error: This is bullet four above and the ref-safe-context of `this`
// in a `struct` is function-member.
public ref int Prop2 => ref _field;
}
Bu, ilk bakışta bir hata gibi görünebilir, ancak bu kasıtlı bir tasarım noktasıdır. Yine de bu, bu teklif tarafından oluşturulan yeni bir kural değildir, bunun yerine geliştiricilerin kendi Span<T>
durumlarını bildirebilecekleri mevcut kuralları ref
kabul etmektedir.
Daha sonra, ref
alanlarının varlığını dikkate alarak referans yeniden atama kurallarının ayarlanması gerekir. Başvurunun tekrar atanması için birincil senaryo, ref struct
oluşturucularının ref
parametrelerini ref
alanlarına atamasıdır. Destek daha genel olacaktır ancak temel senaryo budur. Bunu desteklemek için, referans yeniden atama kuralları aşağıdaki gibi ref
alanlarını hesaba katacak şekilde ayarlanacak:
Referans yeniden atama kuralları
= ref
işlecinin sol işleneni, bir başvuru yerel değişkenine bağlanan bir ifade, bir başvuru parametresi (this
dışında), out parametresi, veya bir başvuru alanıolmalıdır.
Bir referansın
e1 = ref e2
biçiminde yeniden atanması için aşağıdakilerin her ikisi de doğru olmalıdır:
e2
'nin başvuru-güvenli bağlamı, en az 'ine1
kadar büyük olmalıdır.e1
,e2
ile aynı güvenli bağlam'e sahip olmalıdır
Bu, istenen Span<T>
oluşturucunun ek açıklama olmadan çalıştığı anlamına gelir:
readonly ref struct Span<T>
{
readonly ref T _field;
readonly int _length;
public Span(ref T value)
{
// Falls into the `x.e1 = ref e2` case, where `x` is the implicit `this`. The
// safe-context of `this` is *return-only* and ref-safe-context of `value` is
// *caller-context* hence this is legal.
_field = ref value;
_length = 1;
}
}
Başvuru yeniden atama kurallarına yapılan değişiklik, ref
parametrelerin artık ref
bir değerde ref struct
alanı olarak yöntemden kaçabileceği anlamına gelir.
uyumluluk konuları bölümünde açıklandığı gibi bu, ref
parametrelerin ref
alanı olarak kaçmasına yönelik olmayan mevcut API'lerin kurallarını değiştirebilir. Parametrelerin yaşam süresi kuralları yalnızca kullanımlarına değil bildirimlerine dayanır. Tüm ref
ve in
parametreleri, çağıran bağlam içinde ref-safe-context sahiptirler ve bu nedenle de artık ref
veya ref
alanı tarafından döndürülebilir. Kaçabilen veya kaçamayan ref
parametreleri olan API'leri desteklemek ve böylece C# 10 çağrı sitesi semantiğini geri yüklemek için, dil sınırlı yaşam süresi ek açıklamaları ekler.
scoped
modifikatör
Anahtar sözcük scoped
, bir değerin ömrünü kısıtlamak için kullanılır.
ref
'a veya ref struct
olan ve sırasıyla ref-güvenli-bağlam veya güvenli bağlam ömrünü işlev üyesinekısıtlama etkisi olan bir değere uygulanabilir. Örneğin:
Parametre veya Yerel | ref-safe bağlamı | güvenli bağlam |
---|---|---|
Span<int> s |
İşlev üyesi | arayan bağlamı |
scoped Span<int> s |
İşlev üyesi | İşlev üyesi |
ref Span<int> s |
arayan bağlamı | arayan bağlamı |
scoped ref Span<int> s |
İşlev üyesi | arayan bağlamı |
Bu ilişkide, bir değerin ref-güvenli bağlamı hiçbir zaman güvenli bağlamkadar geniş olamaz.
Bu, C# 11'deki API'lerin C# 10 ile aynı kurallara sahip olacak şekilde açıklama eklemesine olanak tanır:
Span<int> CreateSpan(scoped ref int parameter)
{
// Just as with C# 10, the implementation of this method isn't relevant to callers.
}
Span<int> BadUseExamples(int parameter)
{
// Legal in C# 10 and legal in C# 11 due to scoped ref
return CreateSpan(ref parameter);
// Legal in C# 10 and legal in C# 11 due to scoped ref
int local = 42;
return CreateSpan(ref local);
// Legal in C# 10 and legal in C# 11 due to scoped ref
Span<int> span = stackalloc int[42];
return CreateSpan(ref span[0]);
}
scoped
ek açıklaması, bir this
'nin struct
parametresinin artık scoped ref T
olarak tanımlanabileceği anlamına gelir. Daha önce, diğer ref
parametrelerinden farklı ref-safe-context kuralları olan ref
parametresi olarak kurallarda özel olarak belirtilmesi gerekiyordu (alıcıyı güvenli bağlam kurallarına dahil etmeye veya dışlamaya yönelik tüm başvurulara bakın). Artık kurallar boyunca genel bir kavram olarak ifade edilebilir ve bu da onları daha da basitleştirir.
scoped
ek açıklaması aşağıdaki konumlara da uygulanabilir:
- locals: Bu açıklama, başlatıcının ömründen bağımsız olarak yerelin güvenli bağlamya da
ref
ömrüne ya da işlev üyesi ömrüne ayarlanmasını sağlar.
Span<int> ScopedLocalExamples()
{
// Error: `span` has a safe-context of *function-member*. That is true even though the
// initializer has a safe-context of *caller-context*. The annotation overrides the
// initializer
scoped Span<int> span = default;
return span;
// Okay: the initializer has safe-context of *caller-context* hence so does `span2`
// and the return is legal.
Span<int> span2 = default;
return span2;
// The declarations of `span3` and `span4` are functionally identical because the
// initializer has a safe-context of *function-member* meaning the `scoped` annotation
// is effectively implied on `span3`
Span<int> span3 = stackalloc int[42];
scoped Span<int> span4 = stackalloc int[42];
}
Yerel
scoped
ek açıklaması, dönüşler, alanlar, dizi öğeleri gibi başka hiçbir yere uygulanamaz. Ayrıca, scoped
, herhangi bir ref
, in
veya out
'e uygulandığında etkili olmaz; yalnızca ref struct
değerlere uygulandığında etkisi olur.
scoped int
gibi bildirimlerin bulunmasının hiçbir etkisi yoktur çünkü her zaman ref struct
olmayan bir şey geri döndürmek güvenlidir. Derleyici, geliştirici karışıklığını önlemek için bu tür durumlar için bir tanılama oluşturur.
out
parametrelerinin davranışını değiştirme
ref
ve in
parametrelerinin ref
alanları olarak döndürülebilir hale getirilmesinin uyumluluk değişikliğinin etkisini daha da sınırlamak için dil, parametrelerinin varsayılan out
değerini işlev üyesiolarak değiştirir. Etkin bir şekilde out
parametreleri örtük olarak scoped out
, gelecekte. Uyumluluk açısından bakıldığında bu, ref
tarafından döndürülemeyecekleri anlamına gelir:
ref int Sneaky(out int i)
{
i = 42;
// Error: ref-safe-context of out is now function-member
return ref i;
}
Bu, ref struct
değerleri döndüren ve out
parametreleri olan API'lerin esnekliğini artırır çünkü artık başvuruyla yakalanan parametreyi dikkate almak zorunda değildir. Bu, okuyucu stili API'lerde yaygın bir desen olduğundan önemlidir:
Span<byte> Read(Span<byte> buffer, out int read)
{
// ..
}
Span<byte> Use()
{
var buffer = new byte[256];
// If we keep current `out` ref-safe-context this is an error. The language must consider
// the `read` parameter as returnable as a `ref` field
//
// If we change `out` ref-safe-context this is legal. The language does not consider the
// `read` parameter to be returnable hence this is safe
int read;
return Read(buffer, out read);
}
Dil artık bir out
parametresine geçirilen bağımsız değişkenlerin döndürülebilir olmasını dikkate almayacaktır.
out
parametresine girilenleri döndürülebilir olarak kabul etmek, geliştiriciler için son derece kafa karıştırıcıydı. Temel olarak, out
'e saygı göstermeyen diller dışında asla kullanılmayan değerin çağıran tarafından geçirilmesini geliştiricilerin göz önünde bulundurmasını zorunlu kılarak out
amacını alt eder.
ref struct
destekleyen ileriye dönük diller, bir out
parametresine geçirilen özgün değerin hiçbir zaman okunmadığından emin olmalıdır.
C# bunu, kesin atama kuralları aracılığıyla başarıyor. Bu, hem başvuru güvenli bağlam kurallarımıza uyar hem de out
parametre değerlerini atayan ve ardından döndüren mevcut kodun çalışmasına izin verir.
Span<int> StrangeButLegal(out Span<int> span)
{
span = default;
return span;
}
Bu değişiklikler, out
parametresine sağlanan argümanın, yöntem çağrılarına güvenli bağlam veya ref-güvenli bağlam değerleri eklemediği anlamına gelir. Bu, ref
alanlarının genel uyumluluk etkisini önemli ölçüde azaltır ve geliştiricilerin out
hakkında düşünme şeklini basitleştirir. Bir out
parametresinin bağımsız değişkeni, dönüş değerine etki etmez, sadece bir çıktı olarak kullanılır.
Bildirim ifadelerinin güvenli bağlamı içinde ilişkilendir.
bir bağımsız değişkenden (out
) veya yapısızlaştırmadan (M(x, out var y)
) gelen bir bildirim değişkeninin (var x, var y) = M()
, aşağıdakilerin en dar olanıdır .
- çağıran bağlamı
- out değişkeni
scoped
olarak işaretlenirse, o zaman bildirim bloğu (yani işlev üyesi veya daha dar). - out değişkeninin türü
ref struct
ise, alıcı dahil olmak üzere, kapsayıcı çağrıdaki tüm bağımsız değişkenleri dikkate alın.- Herhangi bir argümanın, ilgili parametresinin
olmadığı ve yalnızca dönüş veya daha geniş güvenli bağlam olduğu durumlarda güvenli bağlam herhangi bir bağımsız değişkenin ref-safe-context , ilgili parametresininyalnızca dönüş veya daha geniş ref-safe-contextolduğu durumlarda
- Herhangi bir argümanın, ilgili parametresinin
Ayrıca bkz. bildirim ifadelerinin güvenli bağlam örnekleri.
Örtük olarak scoped
parametreleri
Genel olarak örtük olarak ref
olarak bildirilen iki scoped
konum vardır:
-
this
örnek yöntemindestruct
-
out
Parametreleri
Referans güvenli bağlam kuralları scoped ref
ve ref
şeklinde yazılacaktır. Güvenli başvuru bağlamı için, in
parametresi ref
'e ve out
ise scoped ref
'e eşdeğerdir. Hem in
hem de out
yalnızca kuralın semantiği için önemli olduğunda özel olarak çağrılır. Aksi takdirde yalnızca sırasıyla ref
ve scoped ref
kabul edilirler.
in
yalnızca geçerli kuralın semantiği için önemli olduğunda burada çağrılır.
Yalnızca dönüş güvenli bir bağlam
Tasarım, yeni bir güvenli bağlamın eklenmesini de gerektirir: yalnızca dönüş. Bu,
yalnızca dönüş'in ayrıntıları, işlev üyesi'ten daha büyük, ancak çağıran bağlam'ten daha küçük bir bağlamdır.
return
ifadesine sağlanan ifade, en az yalnızca bir dönüşiçermelidir. Mevcut kuralların çoğu bu şekilde ortaya çıkar. Örneğin,
Varsayılan olarak yalnızca geri dönüş olarak kullanılan üç konum bulunmaktadır:
- bir
veya parametresi yalnızca dönüş ref-safe-context sahip olur. Bu,ref struct
aptalca döngüsel atama sorunlarını önlemek için kısmen yapılır. Modeli basitleştirmek ve uyumluluk değişikliklerini en aza indirmek için tekdüzen olarak yapılır. - bir
için parametresi, yalnızca dönüş güvenli bağlamolacaktır. Bu, iade ve out
eşit şekilde ifade edilebilir olmasını sağlar.out
örtük olarakscoped
olduğundan, başvuru-güvenli bağlam hâlâ güvenli bağlam'ten daha küçük olduğundan, bu saçma döngüsel atama sorunu yoktur. oluşturucu için bir parametresi, yalnızca dönüş güvenli bağlam içerir. Bununout
parametreleri olarak modellenmesi nedeniyle ortaya çıkmasıdır.
Bir yöntemden veya lambdadan açıkça değer döndüren herhangi bir ifade veya deyim, güvenli bağlamve varsa ref-güvenli bağlam, en az sadece dönüşolmalıdır. Buna return
deyimleri, gövdeli ifade üyeleri ve lambda ifadeleri dahildir.
Benzer şekilde, herhangi bir out
'e atama yapıldığında, en az dönüş içingüvenli bağlam olmalıdır. Ancak bu özel bir durum değildir, yalnızca mevcut atama kurallarından takip edilir.
Not: Türü ref struct
türünde olmayan bir ifade, her zaman güvenli bağlam içinde çağıran bağlamsahiptir.
Yöntem çağırma kuralları
Yöntem çağırma için referans güvenli bağlam kuralları çeşitli şekillerde güncellenecek. Birincisi, scoped
bağımsız değişkenler üzerindeki etkisini tanımaktır.
expr
parametresine geçirilen belirli bir bağımsız değişken p
için:
- Eğer
p
scoped ref
ise,expr
bağımsız değişkenler göz önünde bulundurulduğunda ref-safe-context katkıda bulunmaz.- Eğer
p
scoped
ise,expr
bağımsız değişkenler göz önünde bulundurulduğunda güvenli-bağlam'e katkıda bulunmaz.- Eğer
p
out
ise,expr
ref-safe-context veya güvenli bağlam katkı sağlamaz, daha fazla ayrıntı
"Katkıda bulunmaz" ifadesi, scoped
ek açıklaması bunu engellediğinden, değerler bu yaşam süresine katkıda bulunamaz.
Yöntem çağırma kuralları artık basitleştirilebilir. Alıcının artık özel bir muamele görmesi gerekmez, struct
durumunda artık yalnızca bir scoped ref T
'dir. Değer kurallarının, ref
alanı dönüşlerini hesaba katacak şekilde değişmesi gerekiyor.
e1.M(e2, ...)
başvuru-başvuru yapısı döndürmediğiM()
yöntem çağırmasından kaynaklanan bir değer, aşağıdakilerin en darından alınan güvenli bağlam sahiptir:
- çağıran bağlamı
ref struct
olan dönüş, tüm bağımsız değişken ifadelerinin katkıda bulunduğu güvenli bağlam olduğunda- Dönüş
ref struct
olduğunda, tüm bağımsız değişkenlerin katkıda bulunduğuref
M()
başvuru-başvuru yapısı döndürüyorsa, güvenli bağlam, başvuru-başvuru yapısı olan tüm bağımsız değişkenlerin güvenli bağlam ile aynıdır. Birden çok bağımsız değişkenin farklı güvenli bağlamı varsa, yöntem bağımsız değişkenlerinineşleşmesi gerektiğinden hatadır.
ref
çağırma kuralları şu şekilde basitleştirilebilir:
ref-to-ref-yapı döndürmediği sürece, yöntem çağrısından kaynaklanan bir değer, aşağıdaki bağlamların en darı olan ref-safe-context 'dir:
- çağıran bağlamı
- Tüm bağımsız değişken ifadelerinin katkıda bulunduğu güvenli bağlam
- Tüm bağımsız değişkenlerin katkıda bulunduğu
ref
M()
referans-referans yapısını döndürüyorsa, referans-güvenli bağlam, referans-referans yapısı olan tüm bağımsız değişkenlerin katkıda bulunduğu en dar referans-güvenli bağlam olur.
Bu kural artık istenen yöntemlerin iki değişkenini tanımlamamıza olanak tanır:
Span<int> CreateWithoutCapture(scoped ref int value)
{
// Error: value Rule 3 specifies that the safe-context be limited to the ref-safe-context
// of the ref argument. That is the *function-member* for value hence this is not allowed.
return new Span<int>(ref value);
}
Span<int> CreateAndCapture(ref int value)
{
// Okay: value Rule 3 specifies that the safe-context be limited to the ref-safe-context
// of the ref argument. That is the *caller-context* for value hence this is not allowed.
return new Span<int>(ref value);
}
Span<int> ComplexScopedRefExample(scoped ref Span<int> span)
{
// Okay: the safe-context of `span` is *caller-context* hence this is legal.
return span;
// Okay: the local `refLocal` has a ref-safe-context of *function-member* and a
// safe-context of *caller-context*. In the call below it is passed to a
// parameter that is `scoped ref` which means it does not contribute
// ref-safe-context. It only contributes its safe-context hence the returned
// rvalue ends up as safe-context of *caller-context*
Span<int> local = default;
ref Span<int> refLocal = ref local;
return ComplexScopedRefExample(ref refLocal);
// Error: similar analysis as above but the safe-context of `stackLocal` is
// *function-member* hence this is illegal
Span<int> stackLocal = stackalloc int[42];
return ComplexScopedRefExample(ref stackLocal);
}
Nesne başlatıcıları için kurallar
Nesne başlatıcı ifadesinin güvenli bağlam en dar değerdir:
- Oluşturucu çağrısının güvenli bağlamı
. - güvenli bağlam ve ref-güvenli bağlam olan ve alıcıya kaçabilen üye başlatıcı dizinleyicilere yönelik bağımsız değişkenler.
- Üye başlatıcılardaki atamaların rhs değerinin salt okunur olmayan ayarlayıcılara
güvenli bağlam veya başvuru ataması durumunda ref-safe-context.
Bunu modellemenin başka bir yolu, alıcıya atanabilecek üye başlatıcıya yönelik bağımsız değişkenleri oluşturucuya bağımsız değişken olarak düşünmektir. Bunun nedeni, üye başlatıcının etkili bir oluşturucu çağrısı olmasıdır.
Span<int> heapSpan = default;
Span<int> stackSpan = stackalloc int[42];
var x = new S(ref heapSpan)
{
Field = stackSpan;
}
// Can be modeled as
var x = new S(ref heapSpan, stackSpan);
Bu modelleme, MAMM'imizin üye başlatıcılarını özellikle dikkate alması gerektiğini gösterdiğinden önemlidir. Daha dar güvenli bağlam daha yüksek bir değere atanmasına izin verdiğinden, bu özel durumun geçersiz olması gerektiğini düşünün.
Yöntem bağımsız değişkenleri eşleşmelidir
ref
alanlarının varlığı, yöntem bağımsız değişkenlerinin etrafındaki kuralların güncelleştirilmesi gerektiği anlamına gelir; ref
parametresi artık yöntemin ref struct
bağımsız değişkeninde bir alan olarak depolanabilir. Daha önce kural, yalnızca başka bir ref struct
'ın alan olarak depolanmasını dikkate almak zorundaydı. Bu etkinin uyumluluk konularıüzerindeki etkisi tartışılmaktadır. Yeni kural...
Herhangi bir yöntem çağırma
e.M(a1, a2, ... aN)
- En dar güvenli bağlam'i
'dan hesaplayın:
- arayan bağlamı
- Tüm bağımsız değişkenlerin güvenli bağlamı
- İlgili parametreleri çağıran bağlamıref-safe-context sahip olan tüm başvuru bağımsız değişkenlerinin ref-safe-context
ref
türlerinin tümref struct
bağımsız değişkenleri, güvenli bağlamsahip bir değer tarafından atanabilir olmalıdır. Bu,'ın ve 'ü içerecek şekilde genel bir duruma dönüşmemesi halidir.
Herhangi bir yöntem çağırma
e.M(a1, a2, ... aN)
- En dar güvenli bağlam'i
'dan hesaplayın:
- arayan bağlamı
- Tüm bağımsız değişkenlerin güvenli bağlamı
- karşılık gelen parametreleri olmayan tüm başvuru bağımsız değişkenlerinin
scoped
out
türlerinin tümref struct
bağımsız değişkenleri, güvenli bağlamsahip bir değer tarafından atanabilir olmalıdır.
scoped
varlığı, geliştiricilerin scoped
olarak döndürülmeyen parametreleri işaretleyerek bu kuralın oluşturduğu sürtüşmeleri azaltmasına olanak tanır. Bu, yukarıdaki her iki durumda da argümanları (1) çıkarır ve çağıranlara daha fazla esneklik sağlar.
Bu değişikliğin etkisi, 'da'in altında daha ayrıntılı ele alınmaktadır. Genel olarak bu, geliştiricilerin scoped
ile kaçış içermeyen ref benzeri değerlere açıklama ekleyerek çağrı noktalarını daha esnek hale getirmesine olanak sağlar.
Parametre kapsamı varyansı
Parametrelerdeki scoped
değiştirici ve [UnscopedRef]
özniteliği (aşağıdaki bakın), nesne geçersiz kılma, arabirim uygulaması ve delegate
dönüştürme kurallarımızı da etkiler. Geçersiz kılma, arabirim uygulaması veya delegate
dönüştürme imzası şunları yapabilir:
-
scoped
veyaref
parametresinein
ekleme -
scoped
'ıref struct
parametresine ekle -
[UnscopedRef]
parametresindenout
'ı kaldırın - bir
[UnscopedRef]
türününref
parametresindenref struct
kaldırın
scoped
veya [UnscopedRef]
ile ilgili diğer farklar uyuşmazlık olarak kabul edilir.
Derleyici, geçersiz kılmalar, arabirim uygulamaları ve temsilci dönüştürmeleri arasında
- Yöntem,
ref
ekleme (out
kaldırmama) uyuşmazlığı olan,ref struct
türünde bir[UnscopedRef]
veyascoped
parametresine sahiptir. (Bu durumda, aptalca bir döngüsel atama mümkündür, bu nedenle başka parametre gerekmez.) - Ya da bunların ikisi de doğrudur:
- yöntemi bir
ref struct
döndürür veyaref
veyaref readonly
döndürür ya da yönteminref
türünde birout
veyaref struct
parametresi vardır. - yönteminde en az bir ek
ref
,in
veyaout
parametresi ya daref struct
türünde bir parametre vardır.
- yöntemi bir
Tanı aşağıdaki sebeplerle diğer durumlarda bildirilmez:
- Bu tür imzalara sahip yöntemler geçirilen referansları yakalayamaz, bu nedenle kapsamdaki uyuşmazlık tehlikeli değildir.
- Bunlar arasında çok yaygın ve basit senaryolar (örneğin,
out
yöntem imzalarında kullanılan düz eskiTryParse
parametreleri) bulunmaktadır. Dil sürümü 11'de kullanıldıkları için (ve bu nedenleout
parametresinin kapsamı farklı olduğundan) kapsam farklılıklarının bildirilmesi kafa karıştırıcı olabilir.
Eşleşmeyen imzaların her ikisi de C#11 başvuru güvenli bağlam kurallarını kullanıyorsa tanılama hata bildirilir; aksi takdirde, tanılama uyarı'dir.
C#7.2 başvuru güvenli bağlam kurallarıyla derlenen ve scoped
'ın mevcut olmadığı bir modülde kapsam uyuşmazlığı uyarısı bildirilebilir. Bazı durumlarda, eşleşmeyen diğer imza değiştirilemiyorsa uyarının gizlenmesi gerekebilir.
scoped
değiştiricisi ve [UnscopedRef]
özniteliği de yöntem imzaları üzerinde aşağıdaki etkilere sahiptir:
-
scoped
değiştirici ve[UnscopedRef]
özniteliği gizlemeyi etkilemez - Aşırı yüklemeler yalnızca
scoped
veya[UnscopedRef]
üzerinde farklılık gösteremez.
ref
alanı ve scoped
bölümü uzun olduğundan, önerilen köklü değişikliklerin kısa bir özetiyle kapatmak istedim.
-
ref-safe-context olan bir değer, çağıran bağlamı için
ref
veyaref
alanı tarafından döndürülebilir. - Bir
out
parametresi güvenli bağlamişlev üyesi'ün parçası olabilir.
Ayrıntılı Notlar:
-
ref
alanı yalnızca birref struct
içinde bildirilebilir -
ref
alanıstatic
,volatile
veyaconst
bildirilemez -
ref
alanı, birref struct
türe sahip olamaz. - Referans derlemesi oluşturma süreci,
ref
içinde birref struct
alanının mevcudiyetini korumalıdır. -
readonly ref struct
,ref
alanlarınıreadonly ref
olarak bildirmelidir. - Başv değerleri için,
scoped
değiştiriciin
,out
veyaref
'ten önce yer almalıdır. - Span güvenlik kuralları belgesi, bu belgede açıklandığı gibi güncelleştirilecek
- Yeni başvuru güvenli bağlam kuralları şu durumlarda geçerli olur:
- Çekirdek kitaplık,
ref
alanları için desteği gösteren özellik bayrağını içerir -
langversion
değeri 11 veya üzeridir
- Çekirdek kitaplık,
Sözdizimi
13.6.2 Yerel değişken bildirimleri: 'scoped'?
eklendi.
local_variable_declaration
: 'scoped'? local_variable_mode_modifier? local_variable_type local_variable_declarators
;
local_variable_mode_modifier
: 'ref' 'readonly'?
;
13.9.4 for
deyimi: 'scoped'?
local_variable_declaration
dolaylı olarak eklendi.
13.9.5 foreach
deyimi: 'scoped'?
eklendi.
foreach_statement
: 'foreach' '(' 'scoped'? local_variable_type identifier 'in' expression ')'
embedded_statement
;
12.6.2 Bağımsız değişken listeleri: 'scoped'?
bildirim değişkeni için out
eklendi.
argument_value
: expression
| 'in' variable_reference
| 'ref' variable_reference
| 'out' ('scoped'? local_variable_type)? identifier
;
12.7 Dekonstrüksiyon ifadeleri:
[TBD]
15.6.2 Yöntem parametreleri: 'scoped'?
parameter_modifier
'e eklendi.
fixed_parameter
: attributes? parameter_modifier? type identifier default_argument?
;
parameter_modifier
| 'this' 'scoped'? parameter_mode_modifier?
| 'scoped' parameter_mode_modifier?
| parameter_mode_modifier
;
parameter_mode_modifier
: 'in'
| 'ref'
| 'out'
;
12.19 Anonim işlev ifadeleri: 'scoped'?
eklendi.
explicit_anonymous_function_parameter
: 'scoped'? anonymous_function_parameter_modifier? type identifier
;
anonymous_function_parameter_modifier
: 'in'
| 'ref'
| 'out'
;
Gün batımı kısıtlanmış türleri
Derleyici, büyük ölçüde belgelenmemiş bir "kısıtlanmış türler" kümesi kavramına sahiptir. C# 1.0'da davranışlarını ifade etmenin genel amaçlı bir yolu olmadığından bu türlere özel bir durum verilmiştir. En önemlisi, türlerin yürütme yığınına başvurular içerebileceği gerçeğidir. Bunun yerine derleyici bunların özel bilgisine sahipti ve kullanımlarını her zaman güvenli olacak yollarla kısıtlamıştı: izin verilmeyen iadeler, dizi öğeleri olarak kullanılamaz, genel öğelerde kullanamaz, vb...
ref
alanları kullanılabilir ve ref struct
destekleyecek şekilde genişletildikten sonra bu türler ref struct
ve ref
alanlarının birleşimi kullanılarak C# dilinde doğru şekilde tanımlanabilir. Böylece, derleyici bir çalışma zamanının ref
alanlarını desteklediğini algıladığında, artık kısıtlanmış türler kavramına sahip olmaz. Bunun yerine kodda tanımlandığı gibi türleri kullanır.
Bunu desteklemek için başvuru güvenli bağlam kurallarımız aşağıdaki gibi güncelleştirilecektir:
-
__makeref
, imzastatic TypedReference __makeref<T>(ref T value)
ile bir yöntem olarak ele alınacaktır -
__refvalue
,static ref T __refvalue<T>(TypedReference tr)
imzası olan bir yöntem olarak ele alınacaktır.__refvalue(tr, int)
ifadesi, tür parametresi olarak ikinci bağımsız değişkeni etkili bir şekilde kullanır. -
__arglist
, bir parametre olarak, işlev üyesi'nın ref-safe-context ve güvenli bağlam'si olacaktır. - bir ifade olarak
__arglist(...)
, ref-safe-context ve güvenli bağlam ile işlev üyesiolacaktır.
Uyumlu çalışma zamanları, TypedReference
, RuntimeArgumentHandle
ve ArgIterator
ref struct
olarak tanımlanmasını sağlar. Daha da önemlisi, TypedReference
, olası herhangi bir tür için ref
'ye kadar genişleyen bir ref struct
alanı olduğuna göre değerlendirilmelidir (herhangi bir değeri depolayabilir). Bu, yukarıdaki kurallarla birlikte yığına yapılan referansların ömrünü aşmamasını sağlar.
Not: Tam anlamıyla, bu dilin bir parçası değil, bir derleyici uygulama ayrıntısıdır. Ancak ref
alanlarıyla olan ilişki göz önüne alındığında, basitlik için dil önerisine dahil ediliyor.
Kapsamsız temin etme
En önemli sürtünme noktalarından biri, ref
örneği üyelerinde struct
tarafından alanların geri döndürülememesidir. Bu, geliştiricilerin ref
döndüren yöntemler / özellikler oluşturamadıklarından ve alanları doğrudan kullanıma açmak zorunda olduğu anlamına gelir.
ref
'de genellikle en çok istenen, struct
dönüşlerinin yararlılığını azaltır.
struct S
{
int _field;
// Error: this, and hence _field, can't return by ref
public ref int Prop => ref _field;
}
Bu varsayılan için rasyonel makuldür, ancak başvuruyla struct
kaçış this
doğal olarak yanlış bir şey yoktur, yalnızca başvuru güvenli bağlam kuralları tarafından seçilen varsayılan değerdir.
Bu sorunu çözmek için programlama dili, scoped
'i destekleyerek UnscopedRefAttribute
yaşam süresi ek açıklamasının karşıtını sunar. Herhangi bir ref
'a uygulanabilir ve böylece ref-safe-context varsayılan ayarından bir düzey daha geniş olacak şekilde değiştirilir. Örneğin:
UnscopedRef şu objeye uygulandı: [context or object name] | Özgün ref-safe-context | Yeni ref-safe-context |
---|---|---|
örnek üyesi | işlev üyesi | sadece iade |
in
/
ref parametresi |
sadece iade | çağıran bağlamı |
out parametresi |
işlev üyesi | sadece iade |
Bir [UnscopedRef]
örnek yöntemi üzerinde struct
uygularken, örtük this
parametresini değiştirme etkisi yaratır. Bu, this
'ın aynı türdeki ek açıklamasız bir ref
olarak davrandığı anlamına gelir.
struct S
{
int field;
// Error: `field` has the ref-safe-context of `this` which is *function-member* because
// it is a `scoped ref`
ref int Prop1 => ref field;
// Okay: `field` has the ref-safe-context of `this` which is *caller-context* because
// it is a `ref`
[UnscopedRef] ref int Prop1 => ref field;
}
Açıklama, C# 10'un davranışını geri yüklemek amacıyla out
parametrelerine de yerleştirilebilir.
ref int SneakyOut([UnscopedRef] out int i)
{
i = 42;
return ref i;
}
Referans güvenli bağlam kurallarının amaçları doğrultusunda, böyle bir [UnscopedRef] out
yalnızca ref
olarak kabul edilir.
in
'nin yaşam süresi için ref
olarak kabul edilmesine benzer.
[UnscopedRef]
açıklamasına init
içindeki struct
üyeleri ve oluşturucularında izin verilmeyecek. Bu üyeler, ref
üyeleri değiştirilebilir olarak gördükleri için readonly
semantik açısından zaten özeldir. Bu, ref
'ın bu üyelere basit bir ref
olarak, ref readonly
değil, göründüğü anlamına gelir. Oluşturucuların ve init
'ın sınırları içinde buna izin verilir.
[UnscopedRef]
'ı izin vermek, böyle bir ref
'in readonly
semantik gerçekleştikten sonra yanlış bir şekilde oluşturucunun dışına kaçmasına ve mutasyona olanak tanır.
Öznitelik türü aşağıdaki tanıma sahip olacaktır:
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter,
AllowMultiple = false,
Inherited = false)]
public sealed class UnscopedRefAttribute : Attribute
{
}
}
Ayrıntılı Notlar:
-
[UnscopedRef]
ile açıklama eklenmiş bir örnek yöntem veya özellik, çağıran bağlamıolarak belirlenmiş olanthis
ref-safe-context'ye sahiptir. -
[UnscopedRef]
ile açıklanmış bir üye bir arabirimi uygulayamaz. -
[UnscopedRef]
'ı kullanmak bir hatadır.- Bildirilmemiş bir üye
struct
üzerinde -
static
üyesi,init
üyesi veyastruct
kurucu -
scoped
olarak işaretlenmiş bir parametre - Değere göre geçirilen bir parametre
- Referansla geçirilen, örtük olarak kapsamı belirlenmemiş bir parametre
- Bildirilmemiş bir üye
ScopedRefAttribute
scoped
ek açıklamaları, tür System.Runtime.CompilerServices.ScopedRefAttribute
özniteliği aracılığıyla meta veriler halinde yayılır. Özniteliği ad alanı nitelenmiş adıyla eşleştirilir, bu nedenle tanımın belirli bir derlemede görünmesi gerekmez.
ScopedRefAttribute
türü yalnızca derleyici kullanımı içindir; kaynakta buna izin verilmez. Tür bildirimi, derlemeye henüz dahil değilse derleyici tarafından sentezlenmiştir.
Türün tanımı şu şekilde olacaktır:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
internal sealed class ScopedRefAttribute : Attribute
{
}
}
Derleyici bu özniteliği scoped
söz dizimi ile parametresinde yayar. Bu, yalnızca söz dizimi değerin varsayılan durumundan farklı olmasını sağlarsa yayılır. Örneğin scoped out
hiçbir özniteliğin yayılmamasına neden olur.
RefGüvenlikKurallarıNiteliği
C#7.2 ile C#11 arasındaki başvuru güvenli bağlamı kurallarında çeşitli farklılıklar vardır. Bu farklardan herhangi biri, C#10 veya önceki sürümlerle derlenen referanslarla C#11 ile yeniden derlendiği zaman hatalara neden olabilir.
- kapsamsız
ref
/in
/out
parametreleri, C#11'de birref
'nınref struct
alanı olarak bir yöntem çağrısından kaçabilir; bu, C#7.2'de gerçekleşmez. -
out
parametreleri C#11'de örtük olarak kapsam dahilindedir ve C#7.2'de kapsam dışıdır. -
ref
türlerine /in
ref struct
parametrelerinin kapsamı C#11'de örtük olarak ve C#7.2'de kapsamsız
C#11 ile yeniden derleme sırasında uyumsuz değişikliklerin olasılığını azaltmak için C#11 derleyicisini, yöntem bildirimini çözümlemek için kullanılan kurallarla () eşleşen yöntem çağrısı kuralları ( ve
Bunu etkinleştirmek için, modül [module: RefSafetyRules(11)]
veya üzeri ile derlendiğinde veya -langversion:11
alanları için özellik bayrağını içeren bir corlib ile derlendiğinde derleyici yeni bir ref
özniteliği yayar.
Özniteliğe ait bağımsız değişken, modül derlendiğinde kullanılan başvuru güvenli bağlamı kurallarının dil sürümünü gösterir.
Sürüm şu anda derleyiciye geçirilen gerçek dil sürümüne bakılmaksızın 11
olarak sabitlenmiştir.
Beklenti, derleyicinin gelecekteki sürümlerinin referans güvenli bağlam kurallarını güncellemesi ve öznitelikleri farklı sürümlerle belirgin şekilde oluşturmasıdır.
Derleyici,
C#11 derleyicisi bir yöntem çağrısı çözümlediğinde:
- Yöntem bildirimini içeren modül
[module: RefSafetyRules(version)]
içeriyorsa,version
ne olursa olsun yöntem çağrısı C#11 kurallarıyla analiz edilir. - Yöntem bildirimini içeren modül kaynaktan geliyorsa ve
-langversion:11
veyaref
alanları için özellik bayrağını içeren bir corlib ile derlenmişse, yöntem çağrısı C#11 kurallarıyla analiz edilir. - Yöntem bildirimini içeren modül
System.Runtime { ver: 7.0 }
başvuruda bulunursa, yöntem çağrısı C#11 kurallarıyla analiz edilir. Bu kural, C#11 / .NET 7'nin önceki önizlemeleriyle derlenen modüller için geçici bir azaltmadır ve daha sonra kaldırılacaktır. - Aksi takdirde, yöntem çağrısı C#7.2 kurallarıyla analiz edilir.
C#11 öncesi derleyici tüm RefSafetyRulesAttribute
yoksayar ve yöntem çağrılarını yalnızca C#7.2 kurallarıyla analiz eder.
RefSafetyRulesAttribute
, ad alanıyla nitelendirilmiş ismiyle eşleştirilir, bu nedenle tanımın belirli bir derlemede bulunması gerekmez.
RefSafetyRulesAttribute
türü yalnızca derleyici kullanımı içindir; kaynakta buna izin verilmez. Tür bildirimi, derlemeye henüz dahil değilse derleyici tarafından sentezlenmiştir.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public RefSafetyRulesAttribute(int version) { Version = version; }
public readonly int Version;
}
}
Güvenli sabit boyutlu arabellekler
Güvenli sabit boyutlu arabellekler C# 11'de yer almadı. Bu özellik C# uygulamasının gelecekteki bir sürümünde uygulanabilir.
Dil, sabit boyutlu diziler üzerindeki kısıtlamaları rahatlatır; böylece bunlar güvenli kodda bildirilebilir ve öğe türü yönetilebilir veya yönetilemez. Bu, aşağıdaki gibi türleri yasal hale getirir:
internal struct CharBuffer
{
internal char Data[128];
}
Bu bildirimler, unsafe
karşılık gelen parçalarına çok benzer şekilde, içeren türdeki N
öğeler dizisini tanımlar. Bu üyelere bir dizin oluşturucu ile erişilebilir ve Span<T>
ve ReadOnlySpan<T>
örneklerine de dönüştürülebilir.
fixed
türünde bir T
arabelleğe dizin oluştururken kapsayıcının readonly
durumu dikkate alınmalıdır. Kapsayıcı readonly
ise dizin oluşturucu ref readonly T
döndürür, aksi takdirde ref T
döndürür.
Dizin oluşturucu olmadan fixed
arabelleğe erişmenin doğal türü yoktur ancak Span<T>
türlerine dönüştürülebilir. Kapsayıcı
Sonuçta elde edilen Span<T>
örneği, fixed
arabelleğinde bildirilen boyuta eşit bir uzunluğa sahip olur. Döndürülen değerin güvenli bağlamı, arkadaki verilere alan olarak erişildiğinde olacağı gibi, kapsayıcının güvenli bağlamı eşit olacaktır.
Öğe türünün fixed
bir türdeki her T
bildirimi için dil, dönüş türü get
olan karşılık gelen ref T
yalnızca dizin oluşturucu yöntemini oluşturur. Uygulama bildirim türündeki alanları döndüreceği için dizin oluşturucuya [UnscopedRef]
özniteliğiyle ek açıklama eklenir. Üyenin erişilebilirliği, fixed
alanındaki erişilebilirlikle eşleşecektir.
Örneğin, CharBuffer.Data
için dizin oluşturucunun imzası aşağıdaki gibi olacaktır:
[UnscopedRef] internal ref char DataIndexer(int index) => ...;
Sağlanan dizin, fixed
dizisinin bildirilen sınırlarının dışındaysa bir IndexOutOfRangeException
atılır. Sabit bir değer sağlanırsa, bu değer uygun öğeye doğrudan başvuru ile değiştirilir. Sabit, bildirilen sınırların dışında değilse, bu durumda bir derleme zamanı hatası oluşur.
Ayrıca, değer olarak fixed
ve get
işlemleri sağlayan her set
arabelleği için bir adlandırılmış erişimci de üretilecektir. Bu, fixed
arabelleklerinin, ref
erişimcisiyle birlikte, byval get
ve set
işlemlerine sahip olarak mevcut dizi semantiğine daha yakın hale geleceği anlamına gelir. Bu, derleyicilerin fixed
arabelleklerini kullanan kod üretirken, dizilere uyguladıkları esneklikle aynı esnekliği gösterebileceği anlamına gelir. Bu, await
gibi işlemlerin fixed
arabellekler üzerinde daha kolay gerçekleştirilmesini sağlamalıdır.
Bu ayrıca, fixed
arabelleklerin diğer dillerden daha kolay kullanılabilmesini sağlama gibi bir ek avantaj da sunar. Adlandırılmış dizin oluşturucular, .NET'in 1.0 sürümünden bu yana var olan bir özelliktir. Adlandırılmış bir dizin oluşturucuyu doğrudan kullanamayan diller bile bunları genellikle tüketebilir (C# aslında bunun iyi bir örneğidir).
Arabellek için arka plan depolama alanı [InlineArray]
özniteliği kullanılarak oluşturulacak. Bu, sorun 12320'da ele alınan ve özellikle aynı türdeki alanların sırasının verimli bir şekilde bildirilmesine olanak tanıyan bir mekanizmadır. Bu özel sorun hala etkin bir tartışma aşamasındadır ve bu özelliğin uygulanmasının tartışma devam edene kadar devam edeceği beklentisi vardır.
ref
ve new
ifadelerinde with
değerleri olan başlatıcılar
12.8.17.3 Nesne başlatıcılarıbölümünde, dil bilgisini şu şekilde güncelleştiriyoruz:
initializer_value
: 'ref' expression // added
| expression
| object_or_collection_initializer
;
member_initializer
: identifier '=' 'ref' expression // added
| identifier '=' expression
;
Atamanın sol işleneni, bir ref alanına bağlanan bir ifade olmalıdır.
Sağ işlenen, sol işlenenle aynı türde bir değeri belirten bir lvalue üreten bir ifade olmalıdır.
başvuru yerel yeniden atamabenzer bir kural ekliyoruz:
Sol operand bir yazılabilir başvuru türüyse (yani, ref readonly
alanı dışında herhangi bir öğeyi belirlerse), sağ operand yazılabilir bir lvalue olmalıdır.
oluşturucu çağrıları için kaçış kuralları, olarak kalır.
Oluşturucu çağıran bir
new
ifadesi, oluşturmakta olan türü döndürmek için kabul edilen bir yöntem çağrısıyla aynı kurallara uyar.
Yani yukarıda güncelleştirilen çağırma yöntemi kuralları:
Yöntem çağırımının
e1.M(e2, ...)
sonucunda elde edilen rvalue, aşağıdaki bağlamların en küçüğü olan güvenli bağlam'ye sahiptir:
- çağıran bağlamı
- Tüm bağımsız değişken ifadelerinin katkıda bulunduğu güvenli bağlam
- Dönüş
ref struct
olduğunda, tüm argümanların katkıda bulunduğuref
.
Başlatıcıları olan bir new
ifadesi için, başlatıcı ifadeleri (güvenli bağlamakatkıda bulunarak) bağımsız değişken olarak sayılır ve ref
başlatıcı ifadeleri (ref
katkıda bulunarak) bağımsız değişkenleri olarak, özyinelemeli şekilde sayılır.
Güvenli olmayan bağlamdaki değişiklikler
İşaretçi türleri (bölüm 23.3), yönetilen türleri başvuru türü olarak kabul edecek şekilde genişletilmiştir.
Bu tür işaretçi türleri yönetilen bir tür olarak yazılır ve ardından bir *
belirteci gelir. Bir uyarı üretirler.
İşlecin adresi (bölüm 23.6.5) yönetilen türe sahip bir değişkeni işlenen olarak kabul edecek şekilde gevşetilir.
fixed
deyimi (bölüm 23.7), yönetilen tür değişkeninin adresi olan veya yönetilen tür T
öğelerine sahip bir array_type ifadesi olan T
kabul etmek için gevşetilir.
Yığın ayırma başlatıcısı (bölüm 12.8.22) benzer şekilde gevşetilir.
Dikkate Alınacaklar
Geliştirme yığınının bu özelliği değerlendirirken dikkate alması gereken diğer bölümleri de vardır.
UyumlulukLa İlgili Dikkat Edilmesi Gerekenler
Bu teklifteki zorluk, bu tasarımın mevcut yayma güvenlik kurallarımızveya §9.7.2üzerindeki uyumluluk etkileridir. Destekleyen kurallar, ref struct
'ın ref
alanlarına sahip olma kavramını tamamen desteklerken, stackalloc
dışındaki API'lerin yığınla ilgili ref
durumunu yakalamasına izin vermemektedir. Başvuru güvenli bağlam kurallarınınref
parametresinin ref
alanı olarak kaçabilmesini hesaba katmadığını ve bu nedenle aşağıdaki gibi kodlara izin verdiği anlamına gelir.
Span<int> CreateSpanOfInt()
{
// This is legal according to the 7.2 span rules because they do not account
// for a constructor in the form Span(ref T value) existing.
int local = 42;
return new Span<int>(ref local);
}
Bir ref
parametresinin yöntem çağrısından kaçması için üç yol vardır:
- Değere göre dönüş
-
ref
dönüşe göre -
ref
ref struct
ref
/ parametresi olarak döndürülen veya geçirilenout
alanına göre
Mevcut kurallar yalnızca (1) ve (2) için geçerlidir. (3) değerlerini hesaba katmıyorlar, bu nedenle ref
alanları olarak yerel değişkenleri döndürme gibi eksiklikler hesaba katılmaz. Bu tasarım, (3)'ü dikkate almak için kuralları değiştirmelidir. Bu, mevcut API'ler için uyumluluk üzerinde küçük bir etkiye sahip olacaktır. Özellikle aşağıdaki özelliklere sahip API'leri etkiler.
- İmzada bir
ref struct
var- burada
ref struct
bir dönüş türü,ref
veyaout
parametresidir - Alıcı hariç ek bir
in
veyaref
parametresi vardır
- burada
C# 10'da bu tür API'leri çağıranların API'ye ref
durum girişinin bir ref
alanı olarak yakalanabileceğini hiç düşünmesi gerekmedi. Bu özellik, C# 10'da çeşitli desenlerin güvenli bir şekilde var olmasına izin verdi. Ancak, ref
durumunun ref
alanı olarak kaçabilmesi nedeniyle, bu desenler C# 11'de güvensiz hale gelecektir. Örneğin:
Span<int> CreateSpan(ref int parameter)
{
// The implementation of this method is irrelevant when considering the lifetime of the
// returned Span<T>. The ref safe context rules only look at the method signature, not the
// implementation. In C# 10 ref fields didn't exist hence there was no way for `parameter`
// to escape by ref in this method
}
Span<int> BadUseExamples(int parameter)
{
// Legal in C# 10 but would be illegal with ref fields
return CreateSpan(ref parameter);
// Legal in C# 10 but would be illegal with ref fields
int local = 42;
return CreateSpan(ref local);
// Legal in C# 10 but would be illegal with ref fields
Span<int> span = stackalloc int[42];
return CreateSpan(ref span[0]);
}
Bu uyumluluk sonunun etkisinin çok küçük olması beklenir. Etkilenen API şekli, ref
alanlarının olmamasında çok anlamlı olmadığından müşterilerin bunların çoğunu oluşturma olasılığı düşüktür. Bu API şeklini mevcut depolar üzerinde tespit etmek için araçları çalıştıran deneyler, bu iddiayı destekler. Bu biçime sahip önemli sayılara sahip tek depo dotnet/runtime ve bunun nedeni, repo'nun ref
intrinsic türü aracılığıyla ByReference<T>
alanlar oluşturabilmesidir.
Yine de, tasarım bu tür API'lerin varlığını hesaba katmalıdır, çünkü bu geçerli bir deseni ifade eder, ancak pek yaygın bir desen değildir. Bu nedenle tasarım, geliştiricilere C# 10'a yükseltirken mevcut yaşam süresi kurallarını geri yükleme araçları vermelidir. Geliştiricilerin ref
parametrelerini ref
veya ref
alanı tarafından kaçamayacak şekilde açıklamalarına olanak tanıyan mekanizmalar sağlamalıdır. Bu, müşterilerin C# 11'de aynı C# 10 çağrı sitesi kurallarına sahip API'leri tanımlamasına olanak tanır.
Başvuru Derlemeleri
Bu teklifte açıklanan özellikleri kullanan bir derleme için başvuru derlemesi, başvuru güvenli bağlam bilgilerini aktaran öğeleri tutmalıdır. Bu, tüm ömür boyu açıklama niteliklerinin özgün konumlarında korunması gerektiği anlamına gelir. Bunları değiştirme veya atlama girişimleri geçersiz başvuru derlemeleri oluşturabilir.
ref
alanlarını temsil etmek daha nüanslıdır. İdeal olarak bir ref
alanı, diğer tüm alanlarda olduğu gibi bir başvuru derlemesinde görünür. Ancak ref
alanı, meta veri biçiminde yapılan bir değişikliği temsil eder ve bu, bu meta veri değişikliğini anlamak için güncelleştirilmeyen araç zincirleriyle ilgili sorunlara neden olabilir. C++/CLI, bir ref
alanı tüketirse büyük olasılıkla hata veren somut bir örnektir. Bu nedenle, ref
alanların temel kitaplıklarımızdaki başvuru derlemelerinden atlanması avantajlıdır.
ref
alanının tek başına başvuru güvenli bağlam kuralları üzerinde hiçbir etkisi yoktur. Somut bir örnek olarak, mevcut Span<T>
tanımını ref
bir alan kullanacak şekilde çevirmenin tüketim üzerinde hiçbir etkisi olmadığını düşünün. Bu nedenle ref
güvenli bir şekilde atlanabilir. Ancak bir ref
alanının, korunması gereken tüketim üzerinde başka etkileri de vardır:
-
ref struct
alanı olan birref
hiçbir zamanunmanaged
olarak kabul edilmez -
ref
alanının türü sonsuz genel genişletme kurallarını etkiler. Bu nedenle,ref
alanının türü korunması gereken bir tür parametresi içeriyorsa
Bu kurallara göre, ref struct
için geçerli bir referans montaj dönüşümü:
// Impl assembly
ref struct S<T>
{
ref T _field;
}
// Ref assembly
ref struct S<T>
{
object _o; // force managed
T _f; // maintain generic expansion protections
}
Ek Açıklamalar
Yaşam süreleri en doğal olarak türler kullanılarak ifade edilir. Belirli bir programın yaşam süresi, yaşam süresi türleri denetiminde güvenlidir. C# söz dizimi, değerlere ömürleri örtük olarak eklese de, burada temel kuralları açıklayan bir temel tür sistemi vardır. Tasarımdaki değişikliklerin bu kurallar açısından etkilerini tartışmak genellikle daha kolaydır, bu nedenle tartışma amacıyla buraya dahil edilirler.
Bunun 100% eksiksiz bir belge olması gerekmediğini unutmayın. Her davranışı belgeleme burada amaç değildir. Bunun yerine modelin ve modeldeki olası değişikliklerin tartışılabildiği genel bir anlayış ve ortak bir açıklama oluşturmaktır.
Genellikle yaşam süresi türleri hakkında doğrudan konuşmak gerekmez. Özel durumlar, yaşam ömürlerinin belirli "oluşturulma" sitelerine göre değişebildiği yerlerdir. Bu bir tür polimorfizmdir ve bu değişen yaşam süreleri genel parametreler olarak temsil edilen "genel yaşam süreleri" olarak adlandırılır. C# yaşam süresi genel değerlerini ifade etmek için söz dizimi sağlamaz, bu nedenle C# dilinden açık genel parametreler içeren genişletilmiş bir azaltılmış dile örtük bir "çeviri" tanımlarız.
Aşağıdaki örneklerde adlandırılmış yaşam süreleri kullanılır. sözdizimi $a
, a
adlı bir yaşam süresine atıfta bulunur. Tek başına bir anlamı olmayan ancak where $a : $b
söz dizimi aracılığıyla diğer yaşam süreleriyle bir ilişki verilebilen bir yaşam süresidir. Bu, $a
$b
dönüştürülebilir olduğunu gösterir. Bunun, en az $a
kadar uzun bir süre olan $b
'ı belirlemek olarak düşünülmesi faydalı olabilir.
Aşağıda kolaylık ve kısalık için önceden tanımlanmış birkaç yaşam süresi vardır:
-
$heap
: Bu, yığında bulunan herhangi bir değerin ömrüdür. Tüm bağlamlarda ve yöntem imzalarında kullanılabilir. -
$local
: Bu, yöntem yığınında bulunan tüm değerlerin ömrüdür. fonksiyon üyesiiçin etkili bir ad yer tutucu. Yöntemlerde örtük olarak tanımlanır ve herhangi bir çıkış konumu dışında yöntem imzalarında görüntülenebilir. -
$ro
: yalnızca iadesi için ad yer tutucusu -
$cm
: çağıran bağlam için ad yer tutucusu
Yaşam süreleri arasında önceden tanımlanmış birkaç ilişki vardır:
- Tüm yaşam süreleri için
where $heap : $a
$a
where $cm : $ro
-
where $x : $local
tüm önceden tanımlanmış yaşam süreleri için geçerlidir. Kullanıcı tanımlı yaşam sürelerinin açıkça tanımlanmadığı sürece yerel ile ilişkisi yoktur.
Türlerde tanımlandığında yaşam süresi değişkenleri sabit veya kovaryan olabilir. Bunlar genel parametrelerle aynı söz dizimi kullanılarak ifade edilir:
// $this is covariant
// $a is invariant
ref struct S<out $this, $a>
Tür tanımlarında $this
yaşam süresi parametresi önceden tanımlı değildir, ancak tanımlandığı zaman bununla ilişkili birkaç kural vardır.
- Yaşam süresi parametresi ilk sırada olmalıdır.
- Birlikte değişken olmalıdır:
out $this
. -
ref
alanlarının ömrü$this
'e dönüştürülebilir olmalıdır. - Tüm referans olmayan alanların
$this
süresi$heap
veya$this
olmalıdır.
Başvurunun ömrü, başvuruya bir ömür bağımsız değişkeni sağlanarak ifade edilir. Örneğin, yığına atıfta bulunan bir ref
, ref<$heap>
olarak ifade edilir.
Modelde bir oluşturucu tanımlarken yöntem için new
adı kullanılır. Hem döndürülen değer hem de oluşturucu bağımsız değişkenleri için bir parametre listesi olması gerekir. Bu, oluşturucu girişleri ile inşa edilen değer arasındaki ilişkiyi ifade etmek için gereklidir. Model Span<$a><$ro>
yerine Span<$a> new<$ro>
kullanır. Oluşturucuda yaşam süreleri de dahil olmak üzere this
türü, tanımlı dönüş değeri olarak belirlenecektir.
Yaşam süresi için temel kurallar şu şekilde tanımlanır:
- Tüm ömürler, tür argümanlarından önce gelen genel parametreler olarak ifade edilir. Bu,
$heap
ve$local
dışında önceden tanımlanmış yaşam süreleri için geçerlidir. -
T
olmayan tüm türlerref struct
örtük olarakT<$heap>
ömrüne sahiptir. Bu örtük bir işlemdir, her örneğeint<$heap>
yazmanız gerekmez. -
ref
olarak tanımlanan birref<$l0> T<$l1, $l2, ... $ln>
alanı için:-
$l1
'dan$ln
'e kadar tüm yaşam süreleri sabit olmalıdır. -
$l0
yaşam süresi$this
'e dönüştürülebilir olmalıdır
-
-
ref
olarak tanımlanan birref<$a> T<$b, ...>
için$b
'nin$a
'e dönüştürülebilir olması gereklidir. - Bir değişkenin
ref
'sının, aşağıdakiler tarafından tanımlanan bir ömrü vardır:-
ref
yerel, parametre, alan veya türüref<$a> T
olan bir döndürme için ömür$a
'dur. - Tüm başvuru türleri ve başvuru türü alanları için
$heap
- Diğer her şey için
$local
-
- Temel tür dönüştürmesi yasal olduğunda atama veya geri dönüş yasaldır.
- İfadelerin yaşam süreleri, atama ek açıklamaları kullanılarak açık hale getirilebilir:
-
(T<$a> expr)
için değer ömrü açıkça$a
T<...>
-
ref<$a> (T<$b>)expr
değer ömrü$b
içinT<...>
ve başvuru ömrü$a
.
-
Yaşam kuralları amacıyla ref
, dönüştürmeler amacıyla ifade türünün bir parçası olarak kabul edilir. Mantıksal olarak, ref<$a> T<...>
kovaryant ve ref<$a, T<...>>
invaryant olduğu durumda $a
, T
'e dönüştürülerek temsil edilir.
Şimdi C# söz dizimini temel alınan modelle eşlememize olanak sağlayan kuralları tanımlayalım.
Kısalık açısından, açık yaşam süresi parametreleri olmayan bir tür, türün tüm alanlarına tanımlanmış ve uygulanmış out $this
varmış gibi ele alınır.
ref
alanı olan bir tür, açık yaşam süresi parametrelerini tanımlamalıdır.
Bu kurallar, tüm türler için T
'ın scoped T
'e atanabilmesi sabitini desteklemek amacıyla mevcuttur.
T<$a, ...>
'ye dönüştürülebilir olduğu bilinen tüm ömürler için T<$local, ...>
, $local
'e atanabilir. Ayrıca bu, yığındakilere yığından Span<T>
atayabilme gibi diğer öğeleri destekler. Başvuru olmayan değerler için alanların farklı yaşam sürelerine sahip olduğu durumları dışlar; ancak bu, günümüzde C#'ın bir gerçeğidir. Bunun değiştirilmesi, eşlenmesi gereken C# kurallarında önemli bir değişiklik yapılmasını gerektirir.
Bir örnek yöntemi içindeki bir tür this
için S<out $this, ...>
türü örtük olarak aşağıdaki gibi tanımlanır:
- Normal örnek yöntemi için:
ref<$local> S<$cm, ...>
- Örneğin,
[UnscopedRef]
ile açıklama ekleme yöntemi:ref<$ro> S<$cm, ...>
Açık bir this
parametresinin olmamasından dolayı burada örtük kurallar devreye girer. Karmaşık örnekler ve tartışmalar için static
bir yöntem olarak yazmayı ve this
açık bir parametre yapmayı göz önünde bulundurun.
ref struct S<out $this>
{
// Implicit this can make discussion confusing
void M<$ro, $cm>(ref<$ro> S<$cm> s) { }
// Rewrite as explicit this to simplify discussion
static void M<$ro, $cm>(ref<$local> S<$cm> this, ref<$ro> S<$cm> s) { }
}
C# yönteminin söz dizimi modelle aşağıdaki yollarla eşler:
-
ref
parametrenin başvuru ömrü:$ro
-
ref struct
türünden parametreler,$cm
ömrüne sahiptir. - "referans dönüşlerinin referans ömrü
$ro
" -
ref struct
türünün dönüşleri$ro
değer ömrüne sahiptir -
scoped
bir parametrede veyaref
, başvuru ömrünü$local
olacak şekilde değiştirir.
Burada modeli gösteren basit bir örneği inceleyelim:
ref int M1(ref int i) => ...
// Maps to the following.
ref<$ro> int Identity<$ro>(ref<$ro> int i)
{
// okay: has ref lifetime $ro which is equal to $ro
return ref i;
// okay: has ref lifetime $heap which convertible $ro
int[] array = new int[42];
return ref array[0];
// error: has ref lifetime $local which has no conversion to $a hence
// it's illegal
int local = 42;
return ref local;
}
Şimdi ref struct
kullanarak aynı örneği inceleyelim:
ref struct S
{
ref int Field;
S(ref int f)
{
Field = ref f;
}
}
S M2(ref int i, S span1, scoped S span2) => ...
// Maps to
ref struct S<out $this>
{
// Implicitly
ref<$this> int Field;
S<$ro> new<$ro>(ref<$ro> int f)
{
Field = ref f;
}
}
S<$ro> M2<$ro>(
ref<$ro> int i,
S<$ro> span1)
S<$local> span2)
{
// okay: types match exactly
return span1;
// error: has lifetime $local which has no conversion to $ro
return span2;
// okay: type S<$heap> has a conversion to S<$ro> because $heap has a
// conversion to $ro and the first lifetime parameter of S<> is covariant
return default(S<$heap>)
// okay: the ref lifetime of ref $i is $ro so this is just an
// identity conversion
S<$ro> local = new S<$ro>(ref $i);
return local;
int[] array = new int[42];
// okay: S<$heap> is convertible to S<$ro>
return new S<$heap>(ref<$heap> array[0]);
// okay: the parameter of the ctor is $ro ref int and the argument is $heap ref int. These
// are convertible.
return new S<$ro>(ref<$heap> array[0]);
// error: has ref lifetime $local which has no conversion to $a hence
// it's illegal
int local = 42;
return ref local;
}
Şimdi bunun döngüsel kendi kendine atama sorununa nasıl yardımcı olduğunu görelim:
ref struct S
{
int field;
ref int refField;
static void SelfAssign(ref S s)
{
s.refField = ref s.field;
}
}
// Maps to
ref struct S<out $this>
{
int field;
ref<$this> int refField;
static void SelfAssign<$ro, $cm>(ref<$ro> S<$cm> s)
{
// error: the types work out here to ref<$cm> int = ref<$ro> int and that is
// illegal as $ro has no conversion to $cm (the relationship is the other direction)
s.refField = ref<$ro> s.field;
}
}
Şimdi bunun aptal yakalama parametresi sorununa nasıl yardımcı olduğunu görelim:
ref struct S
{
ref int refField;
void Use(ref int parameter)
{
// error: this needs to be an error else every call to this.Use(ref local) would fail
// because compiler would assume the `ref` was captured by ref.
this.refField = ref parameter;
}
}
// Maps to
ref struct S<out $this>
{
ref<$this> int refField;
// Using static form of this method signature so the type of this is explicit.
static void Use<$ro, $cm>(ref<$local> S<$cm> @this, ref<$ro> int parameter)
{
// error: the types here are:
// - refField is ref<$cm> int
// - ref parameter is ref<$ro> int
// That means the RHS is not convertible to the LHS ($ro is not covertible to $cm) and
// hence this reassignment is illegal
@this.refField = ref<$ro> parameter;
}
}
Açık Sorunlar
Uyumluluk bozulmalarını önlemek için tasarımı değiştir
Bu tasarım, mevcut ref-safe-context kurallarımızla çeşitli uyumluluk kesintileri getiriyor. Kırılmaların minimal etkiye sahip olduğuna inanılsa da, hiçbir kırılma değişikliği yapmadan bir tasarıma büyük önem verilmiştir.
Bununla birlikte, uyumluluk koruma tasarımı bu tasarımdan çok daha karmaşıktı. Uyumluluk ref
alanlarını korumak için, ref
ve ref
alana göre döndürme olanağı için farklı yaşam süreleri gerekir. Temelde, bir yöntemin tüm parametreleri için ref-field-safe-context izleme sağlamamız gerekir. Bunun tüm ifadeler için hesaplanması ve ref-safe-context bugün izlendiği her yerde neredeyse tüm değerlerde izlenmesi gerekir.
Ayrıca bu değerin ref-safe-contextile ilişkileri vardır. Örneğin, bir değerin ref
alanı olarak döndürülmesi mantıklı değildir, ancak doğrudan ref
olarak döndürülemez. Bunun nedeni, ref
alanlarının zaten ref
tarafından kolaylıkla döndürülebilmesidir (ref
durumu bir ref struct
içinde, içeren değer döndürülemese bile ref
tarafından döndürülebilir). Bu nedenle kurallar, bu değerlerin birbirine göre duyarlı olduğundan emin olmak için sürekli ayarlamaya ihtiyaç duyar.
Ayrıca, dilin üç farklı yolla döndürülebilecek ref
parametreleri temsil etmesi için söz dizimine ihtiyacı olduğu anlamına gelir: ref
alana göre, ref
ve değere göre. Varsayılan olarak ref
ile geri döndürülebilir. İleride, özellikle ref struct
söz konusu olduğunda, dönüşün ref
alanı veya ref
tarafından daha doğal olması beklenir. Bu, yeni API'lerin varsayılan olarak doğru çalışabilmesi için ek bir söz dizimi açıklamasına ihtiyaç duyacağı anlamına gelir. Bu istenmeyen bir durum.
Ancak bu uyumluluk değişiklikleri aşağıdaki özelliklere sahip yöntemleri etkiler:
- Bir tane
Span<T>
veyaref struct
- burada
ref struct
bir dönüş türü,ref
veyaout
parametresidir - Ek bir
in
veyaref
parametresi vardır (alıcı hariç)
- burada
Etkiyi anlamak için API'leri kategorilere ayırmak yararlı olur:
- Tüketicilerin
ref
'ınref
alanı olarak yakalanmasını hesaba katmasını istiyoruz. İlk örnek,Span(ref T value)
oluşturucularıdır - Tüketicilerin
ref
'ınref
alanı olarak yakalanmasının hesaba katılmasını istemeyin. Ancak bunlar iki kategoriye ayrılır- Güvenli olmayan API'ler. Bunlar,
Unsafe
veMemoryMarshal
türleri içindeki API'lerdir ve bunlarınMemoryMarshal.CreateSpan
en öne çıkanlardır. Bu API'lerref
güvenli olmayan bir şekilde yakalar, ancak bunların güvenli olmayan API'ler olduğu da bilinir. - Güvenli API'ler. Bunlar, etkinlik amacıyla
ref
parametreleri alan, ancak aslında hiçbir yerde kaydedilmeyen API'lerdir. Örnekler küçük ama biriAsnDecoder.ReadEnumeratedBytes
'dır.
- Güvenli olmayan API'ler. Bunlar,
Bu değişiklik öncelikli olarak yukarıdaki avantajlardan (1) yararlanır. Bunların ref
alan ve ileriye dönük bir ref struct
döndüren API'lerin çoğunu oluşturması beklenir. Bu değişiklikler, yaşam süresi kurallarının değişmesi nedeniyle var olan çağrı semantiğini bozarak (2.1) ve (2.2) değerlerini olumsuz etkiler.
Kategoride (2.1) yer alan API'ler ise büyük ölçüde ya Microsoft tarafından ya da ref
alanlarından en çok fayda gören geliştiriciler (Tanner gibi kişiler) tarafından yazılıyor. Bu geliştirici sınıfının, ref
alanlar sağlandığında mevcut semantiği korumak için birkaç açıklama ekleyerek C# 11'e yükseltme yapılırken uyumluluk vergisini kabul edebileceğini varsaymak mantıklıdır.
Kategorideki (2.2) API'ler en büyük sorundur. Bu tür API'lerin sayısı bilinmiyor ve bunların üçüncü taraf kodda daha sık /daha az sık olup olacağı belirsizdir. Beklenti, özellikle out
üzerinde uyumluluk kırılmasını kabul edersek, onların çok az sayıda olması yönündedir. Şimdiye kadar yapılan aramalar, public
yüzey alanında bunlardan çok az sayıda mevcut olduğunu ortaya çıkardı. Bu, anlamsal analiz gerektirdiği için aranacak zor bir desendir. Bu değişikliği almadan önce, az sayıda bilinen durumu etkileyen varsayımları doğrulamak için araç tabanlı bir yaklaşım gerekir.
Her iki kategorideki (2) durum için düzeltmek oldukça basittir. Yakalanabilir olarak değerlendirilmek istenmeyen ref
parametreleri, scoped
'i ref
'ye eklemelidir. (2.1) içinde bu büyük olasılıkla geliştiriciyi Unsafe
veya MemoryMarshal
kullanmaya zorlar, ancak güvenli olmayan stil API'leri için bu beklenir.
İdeal olarak dil, bir API sessizce sorunlu davranışa düştüğünde uyarı vererek sessiz hataya neden olan değişikliklerin etkisini azaltabilir. Bu, hem ref
alıp hem ref struct
döndüren, ancak ref
içinde ref struct
yakalamayan bir yöntem olacaktır. Derleyici bu durumda geliştiricilere bunun yerine ref
scoped ref
olarak ek açıklama eklemeleri gerektiğini bildiren bir tanılama verebilir.
Karar Bu tasarım elde edilebilir ancak sonuçta elde edilen özelliğin, uyumluluk molası verme kararı alındığı noktaya kadar kullanılması daha zordur.
Karar Derleyici, bir yöntem ölçütleri karşıladığında ancak ref
parametresini ref
alanı olarak yakalamadığında bir uyarı sağlar. Bu, müşterileri yükseltme konusunda oluşturdukları olası sorunlar konusunda uygun şekilde uyarmalıdır
Anahtar sözcükler ile öznitelikler karşılaştırması
Bu tasarım, yeni yaşam süresi kurallarına açıklama eklemek için öznitelikleri kullanmayı çağırır. Bu, bağlamsal anahtar sözcüklerle de aynı şekilde kolayca yapılabilirdi. Örneğin [DoesNotEscape]
scoped
ile eşlenebilir. Ancak, bağlamsal anahtar sözcükler bile dahil olmak üzere genellikle çok yüksek bir çıtayı karşılamalıdır. Değerli dil alanını kaplarlar ve dilin daha belirgin parçalarıdır. Bu özellik değerli olsa da C# geliştiricilerinin azınlığa hizmet edecektir.
Yüzeyde anahtar sözcükleri kullanmamayı tercih ediyor gibi görünse de dikkate alınması gereken iki önemli nokta vardır:
- Ek açıklamalar program semantiğini etkiler. Program semantiğini özniteliklerin etkilemesi, C# dilinin temkinli davrandığı bir konudur ve bunun, dilin bu adımı atmasını gerektirecek bir özellik olup olmadığı belirsizdir.
- Geliştiricilerin büyük olasılıkla bu özelliği kullanması, işlev işaretçilerini kullanan geliştirici kümesiyle güçlü bir şekilde kesişmektedir. Bu özellik, geliştiricilerin yalnızca azınlığı tarafından kullanılıyor olsa da yeni bir söz dizimini haklı çıkarıyordu ve bu kararın hâlâ yerinde olduğu düşünülüyor.
Birlikte ele alındığında bu, söz dizimlerinin dikkate alınması gerektiği anlamına gelir.
Söz diziminin kaba bir taslağı şöyle olabilir:
-
[RefDoesNotEscape]
scoped ref
'e haritalanır -
[DoesNotEscape]
scoped
'e haritalanır -
[RefDoesEscape]
unscoped
'e haritalanır
Kararscoped
ve scoped ref
için söz dizimini kullanın; unscoped
için özniteliği kullanın.
Sabit arabellek yerel ayarlarına izin ver
Bu tasarım, herhangi bir türü destekleyebilecek güvenli fixed
arabellekleri sağlar. Buradaki olası uzantılardan biri, bu tür fixed
arabelleklerinin yerel değişkenler olarak bildirilmesine izin vermektir. Bu, birçok mevcut stackalloc
işleminin fixed
arabelleğiyle değiştirilmesine olanak tanır. Bu, ayrıca, stackalloc
yönetilmeyen öğe türleriyle sınırlı olduğu için yığın tarzı tahsisatlara sahip olabileceğimiz senaryolar kümesini genişletir, ancak fixed
arabellekler bu kısıtlamaya tabi değildir.
class FixedBufferLocals
{
void Example()
{
Span<int> span = stackalloc int[42];
int buffer[42];
}
}
Bu durumu bir arada tutar, ancak yerel değişkenler için söz dizimini biraz genişletmemizi gerektirir. Bu ekstra karmaşıklığa değip değmediği net değil. Şimdilik hayır'a karar verebilir ve yeterli ihtiyaç gösteriliyorsa daha sonra geri getirebiliriz.
Bunun yararlı olacağı yer örneği: https://github.com/dotnet/runtime/pull/34149
Karar şimdilik bu işlemden uzak tutun
Modreqs kullanmak mı yoksa kullanmamak mı?
Yeni yaşam süresi öznitelikleriyle işaretlenmiş yöntemlerin yayım sürecinde modreq
'a çevrilip çevrilmemesi gerektiğine dair bir karar verilmelidir. Bu yaklaşım uygulanırsa ek açıklamalar ile modreq
arasında etkili bir 1:1 eşlemesi olacaktır.
modreq
eklemenin mantığı, özniteliklerin başvuru güvenli bağlam kurallarının semantiğini değiştirmesidir. Yalnızca bu semantiği anlayan diller söz konusu yöntemleri çağırmalıdır. Ayrıca, OHI senaryolarına uygulandığında, yaşam süreleri tüm türetilmiş yöntemlerin uygulaması gereken bir sözleşme haline gelir. Ek açıklamaların modreq
olmadan var olması, çakışan yaşam süresi ek açıklamalarına sahip virtual
yöntem zincirlerinin yüklendiği durumlara yol açabilir (bu, yalnızca virtual
zincirinin bir kısmı derlenip diğer kısmı derlenmediğinde meydana gelebilir).
İlk referans güvenli bağlam çalışması modreq
'u kullanmadı, bunun yerine anlamak için dillere ve yazılım çerçevesine güvendi. Aynı zamanda, başvuru güvenli bağlam kurallarına katkıda bulunan tüm öğeler yöntem imzasının güçlü bir parçasıdır: ref
, in
, ref struct
vb. Bu nedenle, bir yöntemin mevcut kurallarında yapılan herhangi bir değişiklik imzada ikili bir değişiklikle sonuç olur. Yeni ömür adlandırmalarına aynı etkiyi sağlamak için modreq
zorlaması gerekir.
Sorun, bunun aşırıya kaçıp kaçmadığıdır.
[DoesNotEscape]
'ın bir parametreye eklenmesi gibi imzaları daha esnek hale getirmenin, gerçekten ikili uyumluluk değişikliğine neden olma gibi olumsuz bir etkisi vardır. Bu da BCL gibi çerçevelerin zaman içinde bu tür imzaları gevşetemeyeceği anlamına gelir. Dilin in
parametreleriyle benimsediği bir yaklaşımı benimseyerek ve yalnızca sanal konumlarda modreq
uygulayarak bir dereceye kadar hafifletilebilir.
Karar Meta verilerde modreq
kullanmayın.
out
ile ref
arasındaki fark modreq
değildir, ancak şu anda farklı referans güvenli bağlam değerlerine sahiptir. Burada modreq
kurallarını kısmen uygulamanın gerçek bir yararı yoktur.
Çok boyutlu sabit arabelleklere izin ver
fixed
arabelleklerin tasarımı çok boyutlu stil dizileri içerecek şekilde genişletilmeli mi? Temelde aşağıdaki gibi bildirimlere imkan tanıma:
struct Dimensions
{
int array[42, 13];
}
Karar Şimdilik izin verme
Kapsamı ihlal etme
Çalışma zamanı deposunda, ref
parametreleri ref
alanları olarak yakalayan genel olmayan birkaç API vardır. Sonuçta elde edilen değerin ömrü izlenmediğinden bunlar güvenli değildir. Örneğin, Span<T>(ref T value, int length)
oluşturucu.
Bu API'lerin çoğu, yalnızca C# 11'e güncellenerek sağlanabilecek olan, geri dönüş değerlerinde uygun yaşam süresi takibi yapmayı tercih edecektir. Ancak, asıl niyetleri güvensiz kalmak olduğundan, bazıları, dönüş değerini izlemeyen mevcut semantiğini korumak isteyecektir. En önemli örnekler MemoryMarshal.CreateSpan
ve MemoryMarshal.CreateReadOnlySpan
. Bu, parametreleri scoped
olarak işaretleyerek elde edilir.
Bu, çalışma zamanının scoped
'ı bir parametreden güvenli olmayan bir şekilde kaldırmak için yerleşik bir desene ihtiyacı olduğu anlamına gelir.
-
Unsafe.AsRef<T>(in T value)
scoped in T value
olarak değiştirerek mevcut amacını genişletebilir. Bu, hemin
hem descoped
parametrelerden kaldırmasına olanak sağlar. Daha sonra "referans güvenliğini kaldırma" evrensel yöntemi haline gelir. - Amacının tamamı
scoped
kaldırmak olan yeni bir yöntem tanıtın:ref T Unsafe.AsUnscoped<T>(scoped in T value)
. Bu, aynı zamandain
'ı da kaldırır çünkü eğer kaldırmasaydı, çağıranlar "referans güvenliğini kaldırmak" için hâlâ yöntem çağrılarının bir kombinasyonuna ihtiyaç duyardı ve bu durumda mevcut çözüm büyük olasılıkla yeterlidir.
Bunun kapsamı varsayılan olarak kaldırılsın mı?
Tasarım, varsayılan olarak scoped
olan iki konuma sahiptir.
-
this
eşittirscoped ref
-
out
eşittirscoped ref
out
ile ilgili karar, ref
alanların uyumluluk yükünü önemli ölçüde azaltmaktır ve aynı zamanda daha doğal bir varsayılandır. Geliştiricilerin, eğer out
verilirse verinin yalnızca dışa doğru aktığını düşünmelerine olanak tanır. Oysa ref
söz konusuysa, kurallar her iki yönde de veri akışını dikkate almalıdır. Bu, önemli geliştirici karışıklığına neden olur.
this
kararı, istenmeyen bir durumdur çünkü bu karar, bir struct
'in ref
tarafından bir alan döndüremeyeceği anlamına gelir. Bu, yüksek performansa sahip geliştiriciler için önemli bir senaryodur ve bu senaryo için temel olarak [UnscopedRef]
özniteliği eklenmiştir.
Anahtar sözcüklerin yüksek bir standardı vardır ve bunu tek bir senaryo için eklemek şüphelidir. Şu düşünceye varılmıştır: Acaba this
'ı varsayılan olarak ref
yapıp scoped ref
olmaktan çıkartarak bu anahtar sözcükten tamamen kaçınabilir miyiz?
this
'ın scoped ref
olması gereken tüm üyeler, bunu sağlamak için yöntemi scoped
ile işaretleyebilir (bugün bir readonly
oluşturmak için bir yöntem readonly ref
olarak işaretlenebilir).
Normal bir struct
'da bu, çoğunlukla olumlu bir değişikliktir çünkü yalnızca bir üyenin ref
dönüşü olduğunda uyumluluk sorunları ortaya çıkar. Bu yöntemlerden çok azı
Bir ref struct
'de bu değişiklik, önemli ölçüde daha fazla uyumluluk sorununa neden olur. Aşağıdakileri göz önünde bulundurun:
ref struct Sneaky
{
int Field;
ref int RefField;
public void SelfAssign()
{
// This pattern of ref reassign to fields on this inside instance methods would now
// completely legal.
RefField = ref Field;
}
static Sneaky UseExample()
{
Sneaky local = default;
// Error: this is illegal, and must be illegal, by our existing rules as the
// ref-safe-context of local is now an input into method arguments must match.
local.SelfAssign();
// This would be dangerous as local now has a dangerous `ref` but the above
// prevents us from getting here.
return local;
}
}
Temel olarak, değiştirilebilirref struct
yerellerdeki tüm örnek yöntemi çağrılarının, yerel daha sonra scoped
olarak işaretlenmedikçe yasadışı olacağı anlamına gelir. Kuralların, alanların this
'deki diğer alanlara yeniden atandığı durumu göz önünde bulundurması gerekir.
readonly ref struct
'da bu sorun yoktur çünkü readonly
doğası başvurunun yeniden atanmasını önler. Yine de bu, neredeyse tüm mevcut değiştirilebilir ref struct
'ları etkilediğinden, geri uyumluluğu önemli ölçüde bozan bir değişiklik olacaktır.
Ancak readonly ref struct
, ref
için ref struct
alanlara sahip olmaya genişlettiğimizde yine de sorunlu olur. Yakalamayı ref
alanının değerine taşıyarak aynı temel sorunun çözümüne olanak tanır:
readonly ref struct ReadOnlySneaky
{
readonly int Field;
readonly ref ReadOnlySpan<int> Span;
public void SelfAssign()
{
// Instance method captures a ref to itself
Span = new ReadOnlySpan<int>(ref Field, 1);
}
}
this
veya üye türüne göre struct
'ın farklı varsayılanlara sahip olması fikri üzerine bazı düşünceler düşünüldü. Örneğin:
-
this
olarakref
:struct
,readonly ref struct
veyareadonly member
-
this
olarakscoped ref
:ref struct
veyareadonly ref struct
ileref
alanınaref struct
Bu, uyumluluk sorunlarını en aza indirir ve esnekliği en üst düzeye çıkarır, ancak müşteriler için durumu karmaşıklaştırma pahasına. Gelecekteki özelliklerden biri olan güvenli fixed
arabellekleri, değiştirilebilir bir ref struct
'in, yalnızca bu tasarımla çalışmayan alanlar için ref
döndürmesi gerektiğinden ve bu nedenle scoped ref
kategorisine gireceğinden dolayı sorunu tam olarak çözmez.
Kararthis
scoped ref
olarak tutun. Bu, yukarıdaki gizli örneklerin derleyici hataları ürettiği anlamına gelir.
ref alanlarını ref yapısına dönüştür
Bu özellik, bir ref
alanının bir ref struct
'e başvurabilmesi sayesinde yeni bir başvuru güvenli bağlam kuralları kümesi açar.
ByReference<T>
'nin bu genel doğası, şimdiye kadar çalışma zamanının böyle bir yapıya sahip olmaması anlamına geliyordu. Sonuç olarak tüm kurallarımız bunun mümkün olmadığı varsayımı ile yazılmıştır.
ref
alanı özelliği büyük ölçüde yeni kurallar oluşturmakla değil, sistemimizdeki mevcut kuralları koordine etmeyle ilgilidir.
ref
alanlarının ref struct
'e izin vermesi, dikkate alınması gereken birkaç yeni senaryo olduğundan yeni kuralları kodlamamızı gerektirir.
Birincisi, bir readonly ref
artık ref
durumunu depolayabilme özelliğine sahip olmasıdır. Örneğin:
readonly ref struct Container
{
readonly ref Span<int> Span;
void Store(Span<int> span)
{
Span = span;
}
}
Bu, yöntem bağımsız değişkenlerini düşünürken, kuralların eşleşmesi gerektiği anlamına gelir; çünkü readonly ref T
, T
için bir ref
alanına sahip olduğunda, ref struct
potansiyel bir yöntem çıktısıdır.
İkinci sorun, dilin yeni bir güvenli bağlam türünü dikkate alması gerektiğidir: ref-field-safe-context. Geçişli olarak bir ref struct
alanı içeren tüm ref
, ref
alanlarındaki değerleri temsil eden başka bir çıkış kapsamına sahiptir. Birden çok ref
alanı söz konusu olduğunda, bunlar tek bir değer olarak toplu olarak izlenebilir. Bunun için parametrelerin varsayılan değeri çağıran bağlam'dir.
ref struct Nested
{
ref Span<int> Span;
}
Span<int> M(ref Nested nested) => nested.Span;
Bu değer kapsayıcının güvenli bağlamı ile ilgili değildir; kapsayıcının bağlamı küçüldükçe, alan değerlerinin ref
üzerinde hiçbir etkisi yoktur. Ayrıca ref-field-safe-context, kapsayıcının güvenli bağlamı'dan asla daha küçük olamaz.
ref struct Nested
{
ref Span<int> Span;
}
void M(ref Nested nested)
{
scoped ref Nested refLocal = ref nested;
// the ref-field-safe-context of local is still *caller-context* which means the following
// is illegal
refLocal.Span = stackalloc int[42];
scoped Nested valLocal = nested;
// the ref-field-safe-context of local is still *caller-context* which means the following
// is still illegal
valLocal.Span = stackalloc int[42];
}
Bu ref-field-safe-context temelde her zaman mevcut olmuştur. Şimdiye kadar ref
alanlar yalnızca normal struct
'i işaret edebildiğinden, bu durum kolayca çağrı bağlamınaindirgenmiştir. Mevcut kurallarımızın, bu yeni ref
dikkate alınarak ref struct
alanlarını desteklemek için güncellenmesi gerekiyor.
Üçüncü olarak, değerler için ref-field-context x.e1 = ref e2
türünün bir e1
olduğu ref struct
için, ref-field-safe-context ile eşdeğer olmalıdır.
Bu sorunlar çok çözülebilir. Derleyici ekibi bu kuralların birkaç sürümünün taslağını çizmiştir ve bunlar büyük ölçüde mevcut analizimizden çıkar. Sorun şu ki, bu tür kuralların doğruluğunu ve kullanılabilirliğini kanıtlamaya yardımcı olan bir tüketici kodu yoktur. Bu, yanlış varsayılanları seçeceğimiz ve çalışma zamanını bu avantajı kullandığında kullanılabilirlik açısından köşeye sıkıştıracağımız korkusu nedeniyle destek eklemekte çok tereddütlü olmamıza neden oluyor. .NET 8 büyük olasılıkla bizi allow T: ref struct
ve Span<Span<T>>
ile bu yönde ittiği için bu endişe özellikle güçlüdür. Kurallar, tüketim koduyla birlikte yapılırsa daha iyi yazılır.
Karar Gecikmesi, ref
alanın .NET 8'e kadar ref struct
edilmesine izin vermeyi geciktiriyor ve bu süreçte söz konusu senaryoların etrafındaki kuralları yönlendirmeye yardımcı olacak senaryolar geliştiriliyor. Bu işlem .NET 9 itibarıyla uygulanmadı
C# 11.0 ne olacak?
Bu belgede özetlenen özelliklerin tek bir geçişte uygulanması gerekmez. Bunun yerine, aşağıdaki demetlerde çeşitli dil sürümleri arasında aşamalar halinde uygulanabilirler:
-
ref
alanları vescoped
[UnscopedRef]
-
ref
alanlarındanref struct
alanlarına - Gün batımı kısıtlanmış türleri
- sabit boyutlu arabellekler
Hangi sürümde nelerin uygulandığı sadece bir kapsam belirleme çalışmasıdır.
Karar Yalnızca (1) ve (2) C# 11.0'u oluşturdu. Gerisi C# dilinin gelecek sürümlerinde dikkate alınacaktır.
Gelecekte Dikkat Edilmesi Gerekenler
Gelişmiş yaşam süresi ek açıklamaları
Bu teklifteki yaşam süresi ek açıklamaları, geliştiricilerin değerlerin varsayılan kaçış/dışarıda kalış davranışını değiştirmesine izin vermeleri konusunda sınırlıdır. Bu, modelimize güçlü bir esneklik getirir, ancak ifade edilebilecek ilişki kümesini kökten değiştirmez. Özünde C# modeli hala temelde ikili bir yapıdadır: Bir değer döndürülebilir mi, yoksa döndürülemez mi?
Bu, sınırlı yaşam süresi ilişkilerinin anlaşılmasını sağlar. Örneğin, bir yöntemden döndürülemez bir değer, bir yöntemden döndürülebilecek bir değerden daha küçük bir yaşam süresine sahiptir. Ancak bir yöntemden döndürülebilecek değerler arasındaki yaşam süresi ilişkisini açıklamanın hiçbir yolu yoktur. Özellikle, bir değerin bir kez oluşturulduktan sonra diğerinden daha uzun bir ömrü olduğunu söylemenin hiçbir yolu yoktur. Her ikisi de bir yöntemden döndürülebilir. Yaşam döngüsü evrimimizdeki bir sonraki adım, bu tür ilişkilerin açıklanması olacaktır.
Rust gibi diğer yöntemler bu tür bir ilişkinin ifade edilmesine olanak sağlar ve bu nedenle daha karmaşık scoped
stil işlemleri uygulayabilir. Böyle bir özellik dahil edilmişse dilimiz de benzer şekilde yararlı olabilir. Şimdilik bunu yapmak için motive edici bir baskı yok, ancak gelecekte olursa, scoped
modelimiz bunu oldukça rahat bir şekilde içerecek şekilde genişletilebilir.
Söz dizimine bir şablon tarzı bağımsız değişkeni eklenerek her scoped
'a adlandırılmış bir yaşam süresi atanabilir. Örneğin scoped<'a>
, yaşam süresi 'a
olan bir değerdir. Daha sonra where
gibi kısıtlamalar bu yaşam süreleri arasındaki ilişkileri açıklamak için kullanılabilir.
void M(scoped<'a> ref MyStruct s, scoped<'b> Span<int> span)
where 'b >= 'a
{
s.Span = span;
}
Bu yöntem 'a
ve 'b
olmak üzere iki yaşam süresi ve aralarındaki ilişkiyi tanımlar, özellikle de 'b
'nin 'a
'ten büyük olduğunu belirtir. Bu, çağrı noktasının, günümüzdeki daha genel kurallara kıyasla, değerlerin yöntemlere nasıl güvenli bir şekilde geçirilebileceği konusunda daha ayrıntılı kurallara sahip olmasını sağlar.
İlgili Bilgiler
Sorun
Aşağıdaki sorunların tümü bu teklifle ilgilidir:
- https://github.com/dotnet/csharplang/issues/1130
- https://github.com/dotnet/csharplang/issues/1147
- https://github.com/dotnet/csharplang/issues/992
- https://github.com/dotnet/csharplang/issues/1314
- https://github.com/dotnet/csharplang/issues/2208
- https://github.com/dotnet/runtime/issues/32060
- https://github.com/dotnet/runtime/issues/61135
- https://github.com/dotnet/csharplang/discussions/78
Teklif
Aşağıdaki teklifler bu teklifle ilgilidir:
Mevcut örnekler
Utf8JsonReader
Bu özel kod parçacığı, bir Span<T>
üzerindeki örnek bir yönteme yığından tahsis edilebilen bir ref struct
geçirirken ortaya çıkan sorunlar nedeniyle "unsafe" anahtar kelimesine ihtiyaç duyar. Bu parametre yakalanmamış olsa bile, dilin olduğunu varsayması gerekir ve bu nedenle burada gerekli olmayan bir şekilde sürtünmeye neden olur.
Bu kod parçacığı, verinin öğelerini işleyerek bir parametreyi değiştirmek istiyor. Kaçış verileri, verimlilik amacıyla yığında bellekte tahsis edilebilir. Parametresinden kaçılmamasına rağmen, derleyici ona bir parametre olduğundan kapsayan yöntemin dışında bir güvenli bağlam atar. Bu, yığın ayırmayı kullanmak için uygulamanın verilerden kaçtıktan sonra parametreye geri atamak için unsafe
kullanması gerektiği anlamına gelir.
Eğlenceli Örnekler
ReadOnlySpan<T>
public readonly ref struct ReadOnlySpan<T>
{
readonly ref readonly T _value;
readonly int _length;
public ReadOnlySpan(in T value)
{
_value = ref value;
_length = 1;
}
}
Frugal listesi
struct FrugalList<T>
{
private T _item0;
private T _item1;
private T _item2;
public int Count = 3;
public FrugalList(){}
public ref T this[int index]
{
[UnscopedRef] get
{
switch (index)
{
case 0: return ref _item0;
case 1: return ref _item1;
case 2: return ref _item2;
default: throw null;
}
}
}
}
Örnekler ve Notlar
Aşağıda kuralların nasıl ve neden çalıştıklarını gösteren bir dizi örnek verilmiştir. Tehlikeli davranışları ve kuralların bunların oluşmasını nasıl engelleyişini gösteren birkaç örnek de dahildir. Teklifte ayarlamalar yaparken bunları mutlaka göz önünde bulundurmak gerekir.
Yeniden atama ve arama siteleri
başvuru yeniden atama ve yöntemi çağırma birlikte nasıl çalıştığını gösterme.
ref struct RS
{
ref int _refField;
public ref int Prop => ref _refField;
public RS(int[] array)
{
_refField = ref array[0];
}
public RS(ref int i)
{
_refField = ref i;
}
public RS CreateRS() => ...;
public ref int M1(RS rs)
{
// The call site arguments for Prop contribute here:
// - `rs` contributes no ref-safe-context as the corresponding parameter,
// which is `this`, is `scoped ref`
// - `rs` contribute safe-context of *caller-context*
//
// This is an lvalue invocation and the arguments contribute only safe-context
// values of *caller-context*. That means `local1` has ref-safe-context of
// *caller-context*
ref int local1 = ref rs.Prop;
// Okay: this is legal because `local` has ref-safe-context of *caller-context*
return ref local1;
// The arguments contribute here:
// - `this` contributes no ref-safe-context as the corresponding parameter
// is `scoped ref`
// - `this` contributes safe-context of *caller-context*
//
// This is an rvalue invocation and following those rules the safe-context of
// `local2` will be *caller-context*
RS local2 = CreateRS();
// Okay: this follows the same analysis as `ref rs.Prop` above
return ref local2.Prop;
// The arguments contribute here:
// - `local3` contributes ref-safe-context of *function-member*
// - `local3` contributes safe-context of *caller-context*
//
// This is an rvalue invocation which returns a `ref struct` and following those
// rules the safe-context of `local4` will be *function-member*
int local3 = 42;
var local4 = new RS(ref local3);
// Error:
// The arguments contribute here:
// - `local4` contributes no ref-safe-context as the corresponding parameter
// is `scoped ref`
// - `local4` contributes safe-context of *function-member*
//
// This is an lvalue invocation and following those rules the ref-safe-context
// of the return is *function-member*
return ref local4.Prop;
}
}
Yeniden atama ve güvenli olmayan kaçışlar
başvuru yeniden atama kuralları aşağıdaki satırın nedeni ilk bakışta belirgin olmayabilir:
e1
, ile aynıe2
sahip olmalıdır
Bunun nedeni, ref
konumların işaret ettiği değerlerin ömrünün sabit olmasıdır. Dolaylılık, burada her türlü varyansa izin vermemizi engelliyor, hatta daha dar yaşam süreleri için bile. Daraltmaya izin veriliyorsa aşağıdaki güvenli olmayan kodu açar:
void Example(ref Span<int> p)
{
Span<int> local = stackalloc int[42];
ref Span<int> refLocal = ref local;
// Error:
// The safe-context of refLocal is narrower than p. For a non-ref reassignment
// this would be allowed as its safe to assign wider lifetimes to narrower ones.
// In the case of ref reassignment though this rule prevents it as the
// safe-context values are different.
refLocal = ref p;
// If it were allowed this would be legal as the safe-context of refLocal
// is *caller-context* and that is satisfied by stackalloc. At the same time
// it would be assigning through p and escaping the stackalloc to the calling
// method
//
// This is equivalent of saying p = stackalloc int[13]!!!
refLocal = stackalloc int[13];
}
ref
olmayan bir ref struct
için, değerlerin tümü aynı güvenli bağlamsahip olduğundan bu kural kolayca karşılanır. Bu kural gerçekten yalnızca değer bir ref struct
olduğunda devreye girer.
Bu ref
davranışı, ref
alanlarına ref struct
izin verebileceğimiz bir gelecekte de önemli olacaktır.
kapsamı belirlenmiş yereller
Yerellerde scoped
kullanımı, yerel ayarlara farklı güvenli bağlam sahip değerleri koşullu olarak atayan kod desenleri için özellikle yararlı olacaktır. Bu, kodun artık = stackalloc byte[0]
tanımlamak için gibi başlatma püf noktalarına bel bağlamasına gerek olmadığı, bunun yerine artık basitçe scoped
kullanabileceği anlamına gelir.
// Old way
// Span<byte> span = stackalloc byte[0];
// New way
scoped Span<byte> span;
int len = ...;
if (len < MaxStackLen)
{
span = stackalloc byte[len];
}
else
{
span = new byte[len];
}
Bu düzen düşük düzeyli kodda sık sık ortaya çıkar. İlgili ref struct
Span<T>
yukarıdaki püf noktası kullanılabilir. Ancak diğer ref struct
türleri için geçerli değildir ve düşük seviyeli kodun, ömrü doğru bir şekilde belirleyememe nedeniyle unsafe
kullanması gerekebilir.
kapsamlı parametre değerleri
Düşük düzeyli kodda yinelenen sürtüşmelerden biri, parametreler için varsayılan kaçışın izin verilebilir olmasıdır. ref struct
'nın daha sık kullanılmasına rağmen bu varsayılan, referans güvenli bağlam kurallarının diğer bölümleriyle sürtüşmelere neden olabilir.
Ana sürtünme noktası, yöntemi bağımsız değişkenlerinin kuralıyla uyuşması gerektiği için meydana gelir. Bu kural, en yaygın olarak en az bir parametrenin de ref struct
olduğu ref struct
üzerindeki örnek yöntemlerle devreye girer. Bu, düşük düzeyli kodda ref struct
türlerinin yöntemlerindeki Span<T>
parametrelerini sıkça kullandığı yaygın bir desendir. Örneğin, ref struct
kullanarak arabellekleri geçiren herhangi bir yazım stili Span<T>
'da gerçekleşir.
Aşağıdaki gibi senaryoları önlemek için bu kural vardır:
ref struct RS
{
Span<int> _field;
void Set(Span<int> p)
{
_field = p;
}
static void DangerousCode(ref RS p)
{
Span<int> span = stackalloc int[] { 42 };
// Error: if allowed this would let the method return a reference to
// the stack
p.Set(span);
}
}
Temelde bu kural mevcut çünkü dil, bir yönteme yönelik tüm girişlerin izin verilen en yüksek güvenli bağlamkaçtığını varsaymalıdır. Alıcılar dahil olmak üzere ref
veya out
parametreleri olduğunda, girişlerin bu ref
değerlerinin alanlarına dönüşmesi mümkündür (yukarıdaki RS.Set
'te olduğu gibi).
Uygulamada, birçok yöntem ref struct
'ı parametre olarak iletir, ancak bunlar hiçbir zaman sonucu etkilemeyi amaçlamaz. Yalnızca geçerli yöntem içinde kullanılan bir değerdir. Örneğin:
ref struct JsonReader
{
Span<char> _buffer;
int _position;
internal bool TextEquals(ReadOnlySpan<char> text)
{
var current = _buffer.Slice(_position, text.Length);
return current == text;
}
}
class C
{
static void M(ref JsonReader reader)
{
Span<char> span = stackalloc char[4];
span[0] = 'd';
span[1] = 'o';
span[2] = 'g';
// Error: The safe-context of `span` is function-member
// while `reader` is outside function-member hence this fails
// by the above rule.
if (reader.TextEquals(span))
{
...
}
}
}
Bu düşük düzeyli kodu geçici olarak çözmek için derleyiciye unsafe
ömrü hakkında yalan söylemeleri için ref struct
püf noktalarına başvuracaktır. Bu, yüksek performanslı kod yazmaya devam ederken ref struct
'i önlemenin bir yolu olması gerektiği için unsafe
'ın değer önerisini önemli ölçüde azaltır.
scoped
, güncellenmiş ref struct
kuralıyla eşleşmesi gerektiğinden, parametrelerini yöntemden döndürülmek üzere değerlendirme dışı bırakan etkili bir araçtır. Çağrı sitelerini daha esnek hale getirmek için tüketilen ancak hiç döndürülmeyen bir ref struct
parametresi scoped
olarak etiketlenebilir.
ref struct JsonReader
{
Span<char> _buffer;
int _position;
internal bool TextEquals(scoped ReadOnlySpan<char> text)
{
var current = _buffer.Slice(_position, text.Length);
return current == text;
}
}
class C
{
static void M(ref JsonReader reader)
{
Span<char> span = stackalloc char[4];
span[0] = 'd';
span[1] = 'o';
span[2] = 'g';
// Okay: the compiler never considers `span` as capturable here hence it doesn't
// contribute to the method arguments must match rule
if (reader.TextEquals(span))
{
...
}
}
}
Karmaşık başvuru atamalarının salt okunur mutasyona uğramasını önleme
Bir ref
, bir oluşturucu veya readonly
üyedeki init
alanına alındığında türü ref
değil, ref readonly
olur. Bu, aşağıdaki gibi kodlara izin veren uzun süredir devam eden bir davranıştır:
struct S
{
readonly int i;
public S(string s)
{
M(ref i);
}
static void M(ref int i) { }
}
Ancak bu tür bir ref
aynı türdeki bir ref
alanında depolanabiliyorsa bu durum olası bir sorun oluşturur. Örnek üyesinden bir readonly struct
doğrudan mutasyona izin verir:
readonly ref struct S
{
readonly int i;
readonly ref int r;
public S()
{
i = 0;
// Error: `i` has a narrower scope than `r`
r = ref i;
}
public void Oops()
{
r++;
}
}
Teklif, referans güvenli bağlam kurallarını ihlal ettiği için bunu önler. Aşağıdakileri göz önünde bulundurun:
-
this
'dir ve güvenli-bağlam, işlev üyesi ve çağırıcı-bağlam'dir. Bunların ikisi dethis
üyedekistruct
için standarttır. 'nin ref-güvenli-bağlamı ,fonksiyon-üyesidir. Bu, alan ömrü kurallarına göre'den kaynaklanıyor. Özellikle kural 4.
Bu noktada r = ref i
satırı, atıf yeniden atama kurallarıtarafından geçersizdir.
Bu kurallar bu davranışı önlemek için tasarlanmamıştır, ancak bunu bir yan etki olarak yapar. Bu gibi senaryoların etkisini değerlendirmek için gelecekteki tüm kural güncelleştirmelerinde bunu göz önünde bulundurmak önemlidir.
Aptal döngüsel atama
Bu tasarımın zorlandığı bir yön, bir ref
'in bir yöntemden ne kadar serbestçe döndürülebileceğidir. Geliştiricilerin çoğunun sezgisel olarak beklediği şey, tüm ref
'nun normal değerler kadar serbestçe döndürülmesini sağlamaktır. Ancak, referans güvenliğini hesaplarken derleyicinin dikkate alması gereken patolojik senaryolara olanak tanır. Aşağıdakileri göz önünde bulundurun:
ref struct S
{
int field;
ref int refField;
static void SelfAssign(ref S s)
{
// Error: s.field can only escape the current method through a return statement
s.refField = ref s.field;
}
}
Bu, geliştiricilerin kullanmasını beklediğimiz bir kod düzeni değildir. Ancak bir ref
, bir değerle aynı yaşam süresine sahip olarak döndürülebildiğinde, kurallar kapsamında yasaldır. Derleyici, bir yöntem çağrısını değerlendirirken tüm yasal durumları dikkate almalıdır ve bu da bu TÜR API'lerin etkili bir şekilde kullanılamaz olmasına neden olur.
void M(ref S s)
{
...
}
void Usage()
{
// safe-context to caller-context
S local = default;
// Error: compiler is forced to assume the worst and concludes a self assignment
// is possible here and must issue an error.
M(ref local);
}
Bu API'leri kullanılabilir hale getirmek için derleyici, ref
parametresi için ref
ömrünün, ilişkili parametre değerindeki başvuruların ömründen daha kısa olmasını garanti eder. Bu,
[UnscopedRef]
, çağıran bağlamı değerleri için ref
ref struct
yükseltir ve bu nedenle döngüsel atamaya izin verir ve çağrı zincirini [UnscopedRef]
viral kullanımını zorlar:
S F()
{
S local = new();
// Error: self assignment possible inside `S.M`.
S.M(ref local);
return local;
}
ref struct S
{
int field;
ref int refField;
public static void M([UnscopedRef] ref S s)
{
// Allowed: s has both safe-context and ref-safe-context of caller-context
s.refField = ref s.field;
}
}
Benzer şekilde
Tür [UnscopedRef] ref
bir olmadığında, 'ı ref struct
'ye yükseltmek faydalıdır (kuralları basit tutmak istediğimizden, başvuruları referans olmayan yapılardan ayırt etmeyeceklerini unutmayın):
int x = 1;
F(ref x).RefField = 2;
Console.WriteLine(x); // prints 2
static S F([UnscopedRef] ref int x)
{
S local = new();
local.M(ref x);
return local;
}
ref struct S
{
public ref int RefField;
public void M([UnscopedRef] ref int data)
{
RefField = ref data;
}
}
Gelişmiş ek açıklamalar açısından [UnscopedRef]
tasarımı aşağıdakileri oluşturur:
ref struct S { }
// C# code
S Create1(ref S p)
S Create2([UnscopedRef] ref S p)
// Annotation equivalent
scoped<'b> S Create1(scoped<'a> ref scoped<'b> S)
scoped<'a> S Create2(scoped<'a> ref scoped<'b> S)
where 'b >= 'a
readonly, başvuru alanları aracılığıyla derinlemesine yapılamaz
Aşağıdaki kod örneğini göz önünde bulundurun:
ref struct S
{
ref int Field;
readonly void Method()
{
// Legal or illegal?
Field = 42;
}
}
Bir vakumda ref
örneklerde readonly
alanları için kurallar tasarlanırken, kurallar geçerli olarak yukarıdakilerin yasal veya yasa dışı olması için tasarlanabilir. Temelde readonly
, bir ref
alanında derinlemesine işleyebilir veya yalnızca ref
'ye uygulanabilir. Yalnızca ref
uygulanması, referansın yeniden atanmasını engeller, ancak referanslanan değeri değiştiren normal atamaya izin verir.
Ancak bu tasarım vakumda var olan bir şey değildir; zaten etkin bir şekilde ref
alanlarına sahip türler için kurallar tasarlamaktadır. En göze çarpan Span<T>
, readonly
'in burada derin olmamasına zaten güçlü bir bağımlılık gösteriyor. Birincil senaryosu, ref
örneği aracılığıyla readonly
alanına atama yapabilmektir.
readonly ref struct SpanOfOne
{
readonly ref int Field;
public ref int this[int index]
{
get
{
if (index != 1)
throw new Exception();
return ref Field;
}
}
}
Bu, readonly
yüzeysel yorumunu seçmemiz gerektiği anlamına gelir.
Modelleme oluşturucuları
İnce tasarım sorularından biri şudur: Yapıcı gövdeleri ref güvenliği için nasıl modellenir? Temel olarak aşağıdaki oluşturucu nasıl analiz edilir?
ref struct S
{
ref int field;
public S(ref int f)
{
field = ref f;
}
}
Kabaca iki yaklaşım vardır:
güvenli bağlam çağıran bağlamyerel olduğu bir yöntemi olarak model oluşturma - "
static
yöntemi olarak,this
'in birout
parametresi olduğu modeli oluşturun."
Ayrıca bir oluşturucu, aşağıdaki değişmezleri karşılamalıdır:
-
ref
parametrelerininref
alanları olarak yakalanaadığından emin olun. -
ref
alanlarının,this
parametreleriyleref
'e kaçamayacağından emin olun. Bu, karmaşık başvuru atamasını ihlal eder.
Amaç, oluşturucular için herhangi bir özel kural kullanılmadan sabit değerlerimizi karşılayan formu seçmektir. Oluşturucular için en iyi modelin this
bir out
parametresi olarak görüntülemek olduğu düşünüldüğünde.
yalnızca doğasını döndüren out
, yukarıdaki tüm invariantları özel bir duruma ihtiyaç duymadan karşılamamızı sağlar.
public static void ctor(out S @this, ref int f)
{
// The ref-safe-context of `ref f` is *return-only* which is also the
// safe-context of `this.field` hence this assignment is allowed
@this.field = ref f;
}
Yöntem bağımsız değişkenleri eşleşmelidir
Yöntem argümanlarının kuralla uyumlu olması gerektiği kuralı, geliştiriciler için yaygın bir karışıklık kaynağıdır. Kuralın arkasındaki mantığı bilmiyorsanız anlaşılması zor olan bir dizi özel durum içeren bir kuraldır. Kuralın nedenlerini daha iyi anlamak için başvuru-güvenli bağlam ve güvenli bağlam'ü sadece bağlamolarak basitleştireceğiz.
Yöntemler, parametre olarak kendilerine geçirilen durumu oldukça liberal bir şekilde döndürebilir. Temelde, kapsam dışı olan herhangi bir ulaşılabilir durum ( ref
ile döndürülme dahil) döndürülebilir. Bu, doğrudan bir return
deyimi aracılığıyla veya ref
değerine atanarak dolaylı olarak döndürülebilir.
Doğrudan iadeler, geri dönüş güvenliği için çok fazla sorun oluşturmaz. Derleyicinin yalnızca bir yönteme döndürülen tüm girişlere bakması gerekir ve ardından dönüş değerini girişin en düşük bağlam olacak şekilde etkili bir şekilde kısıtlar. Bu dönüş değeri normal işlemeden geçer.
Tüm ref
metot için hem giriş hem de çıkış olduklarından dolaylı dönüşler önemli bir sorun oluşturur. Bu çıkışlarınbilinen bir ref
güvenliğini ihlal edebileceğinden yasa dışı olması gerekir.
Yöntem bağımsız değişkenleri, derleyicinin bu güvenlik denetimini onaylama işlemiyle eşleşmelidir.
Bunu değerlendirmenin geliştiricilerin dikkate almaları genellikle daha kolay olan farklı bir yöntem aşağıdaki alıştırmayı yapmaktır:
- Yöntem tanımına bakın, durumun dolaylı olarak döndürülebileceği tüm yerleri belirleyin: a.
ref
b'ye işaret eden değiştirilebilirref struct
parametreleri. Atanabilir başvururef
alanlarına sahip değiştirilebilirref
parametreleri.ref
işaret eden atanabilirref
parametreleri veyaref struct
alanları (özyinelemeli olarak göz önünde bulundurun) - Arama sitesi A'ya bakın. B'nin üzerinde tanımlanan konumlarla hizalanan bağlamları belirleyin. Yöntemine döndürülen tüm girişlerin bağlamlarını tanımlayın (
scoped
parametreleriyle hizalanmayın)
2.b'deki herhangi bir değer 2.a'dan küçükse yöntem çağrısı geçersiz olmalıdır. Şimdi kuralları göstermek için birkaç örneğe bakalım:
ref struct R { }
class Program
{
static void F0(ref R a, scoped ref R b) => throw null;
static void F1(ref R x, scoped R y)
{
F0(ref x, ref y);
}
}
F0
çağrısına baktığımızda (1) ve (2) üzerinden geçelim. Dolaylı dönüş potansiyeline sahip parametreler, her ikisi de doğrudan atanabileceği için a
ve b
'dir. Bu parametrelere göre sıralanan bağımsız değişkenler şunlardır:
-
a
,x
'i olan, çağıran-bağlamı ile 'e eşlenir. -
b
,y
'nin bağlamı ile sahip olduğu ile eşlendiği
yöntemine döndürülebilir giriş kümesi şunlardır:
-
x
ile kaçış kapsamıçağıran bağlamı -
ref x
ile kaçış kapsamıçağıran bağlamı -
y
, kaçış kapsamı olan işlev üyesi
ref y
değeri, bir scoped ref
'e eşlendiği için döndürülemez ve bu nedenle bir giriş olarak kabul edilmez. Ancak, en az bir girdinin ( bağımsız değişkeni) çıkışlardan birinin (y
bağımsız değişkeni) sahip olduğundan daha küçük bir x
olduğu göz önüne alındığında, yöntem çağrısı geçerli değildir.
Farklı bir varyasyon aşağıdaki gibidir:
ref struct R { }
class Program
{
static void F0(ref R a, ref int b) => throw null;
static void F1(ref R x)
{
int y = 42;
F0(ref x, ref y);
}
}
Tekrar dolaylı dönüş potansiyeli olan parametreler, her ikisi de doğrudan atanabileceği için a
ve b
'dir. Ancak b
, bir ref struct
işaret etmediğinden dışlanabilir, bu nedenle ref
durumunu depolamak için kullanılamaz. Bu nedenle:
-
a
,x
'i olan, çağıran-bağlamı ile 'e eşlenir.
yöntemine döndürülebilir giriş kümesi şunlardır:
-
x
ile bağlamçağıran bağlamı -
ref x
ile bağlamçağıran bağlamı -
ref y
ile bağlamişlev üyesi
En az bir girişin ( bağımsız değişkeni), çıkışlardan birinin (ref y
bağımsız değişkeni) daha küçük bir x
'ye sahip olması durumunda, yöntem çağrısı yasadışıdır.
Yöntem bağımsız değişkenlerinin kuralla eşleşmesi gerektiği mantık işte budur. Hem girişlerin dikkate alınmaktan çıkarılmasının bir yolu olarak scoped
, hem de readonly
’nin çıkış olarak kaldırılmasının bir yolu olarak ref
düşünülerek daha da ileri gider (readonly ref
'e atanamaz, bu yüzden çıkış kaynağı olamaz). Bu özel durumlar kurallara karmaşıklık katar, ancak bunu geliştiricinin yararına yapar. Derleyici, bir üyeyi çağırırken geliştiricilere maksimum esneklik sağlamak için sonuca katkıda bulunamayacaını bildiği tüm girişleri ve çıkışları kaldırmaya çalışır. Aşırı yük çözümlemesi gibi, tüketicilere daha fazla esneklik sağladığında kurallarımızı daha karmaşık hale getirmek için çaba harcamaya değer.
Bildirim ifadelerinin çıkarımlı güvenli bağlam örnekleri
bildirim ifadelerinin güvenli bağlamı ile çıkarım yapma ile ilgilidir.
ref struct RS
{
public RS(ref int x) { } // assumed to be able to capture 'x'
static void M0(RS input, out RS output) => output = input;
static void M1()
{
var i = 0;
var rs1 = new RS(ref i); // safe-context of 'rs1' is function-member
M0(rs1, out var rs2); // safe-context of 'rs2' is function-member
}
static void M2(RS rs1)
{
M0(rs1, out var rs2); // safe-context of 'rs2' is function-member
}
static void M3(RS rs1)
{
M0(rs1, out scoped var rs2); // 'scoped' modifier forces safe-context of 'rs2' to the current local context (function-member or narrower).
}
}
scoped
değiştiricisinden elde edilen yerel bağlamın değişken için kullanılabilecek en dar bağlam olduğunu unutmayın; daha dar olması, ifadenin yalnızca ifadeden daha dar bir bağlamda bildirilen değişkenlere başvurduğu anlamına gelir.
C# feature specifications