Yerel işlevler (C# Programlama Kılavuzu)
Yerel işlevler , başka bir üyede iç içe yerleştirilmiş bir türün yöntemleridir. Bunlar yalnızca kendi içeren üyelerinden çağrılabilir. Yerel işlevler içinde bildirilebilir ve şu kaynaktan çağrılabilir:
- Yöntemler, özellikle yineleyici yöntemler ve zaman uyumsuz yöntemler
- Oluşturucular
- Özellik erişimcileri
- Olay erişimcileri
- Anonim yöntemler
- Lambda ifadeleri
- Sonlandırıcılar
- Diğer yerel işlevler
Ancak, yerel işlevler ifade gövdeli bir üye içinde bildirilemiyor.
Not
Bazı durumlarda, yerel işlev tarafından da desteklenen işlevleri uygulamak için lambda ifadesi kullanabilirsiniz. Karşılaştırma için bkz . Yerel işlevler ve lambda ifadeleri.
Yerel işlevler kodunuzun amacını net hale getirir. Kodunuzu okuyan herkes, yöntemini içeren yöntem dışında çağrılamaz olduğunu görebilir. Ekip projeleri için, başka bir geliştiricinin yöntemi doğrudan sınıfın veya yapının başka bir yerinden yanlışlıkla çağırmasını da imkansız hale getirir.
Yerel işlev söz dizimi
Yerel işlev, içeren bir üyenin içinde iç içe yerleştirilmiş bir yöntem olarak tanımlanır. Tanımı aşağıdaki söz dizimine sahiptir:
<modifiers> <return-type> <method-name> <parameter-list>
Not
<parameter-list>
, bağlamsal anahtar sözcüğüyle value
adlı parametreleri içermemelidir.
Derleyici, başvurulan dış değişkenleri içeren geçici "value" değişkenini oluşturur. Bu değişken daha sonra belirsizliğe neden olur ve beklenmeyen davranışlara da neden olabilir.
Aşağıdaki değiştiricileri yerel bir işlevle kullanabilirsiniz:
async
unsafe
static
Statik bir yerel işlev yerel değişkenleri veya örnek durumunu yakalayamaz.extern
Dış yerel işlev olmalıdırstatic
.
Yöntem parametreleri dahil olmak üzere, içeren üyede tanımlanan tüm yerel değişkenlere statik olmayan bir yerel işlevde erişilebilir.
Yöntem tanımından farklı olarak, yerel işlev tanımı üye erişim değiştiricisini içeremez. Anahtar sözcüğü gibi private
bir erişim değiştirici de dahil olmak üzere tüm yerel işlevler özel olduğundan, "'özel' değiştirici bu öğe için geçerli değil" derleyici hatası CS0106 oluşturur.
Aşağıdaki örnek adlı bir yöntemin özel olduğu adlı AppendPathSeparator
GetText
bir yerel işlevi tanımlar:
private static string GetText(string path, string filename)
{
var reader = File.OpenText($"{AppendPathSeparator(path)}{filename}");
var text = reader.ReadToEnd();
return text;
string AppendPathSeparator(string filepath)
{
return filepath.EndsWith(@"\") ? filepath : filepath + @"\";
}
}
Aşağıdaki örnekte gösterildiği gibi yerel işleve, parametrelerine ve tür parametrelerine öznitelikler uygulayabilirsiniz:
#nullable enable
private static void Process(string?[] lines, string mark)
{
foreach (var line in lines)
{
if (IsValid(line))
{
// Processing logic...
}
}
bool IsValid([NotNullWhen(true)] string? line)
{
return !string.IsNullOrEmpty(line) && line.Length >= mark.Length;
}
}
Yukarıdaki örnek, derleyicinin boş değer atanabilir bir bağlamda statik çözümlemede yardımcı olması için özel bir öznitelik kullanır.
Yerel işlevler ve özel durumlar
Yerel işlevlerin kullanışlı özelliklerinden biri, özel durumların hemen ortaya çıkışına izin verebilmeleridir. Yineleyici yöntemleri için özel durumlar yalnızca döndürülen sıra numaralandırıldığında ortaya çıkar ve yineleyici alındığında gösterilmez. Zaman uyumsuz yöntemler için, döndürülen görev beklenirken zaman uyumsuz bir yöntemde oluşan özel durumlar gözlemlenir.
Aşağıdaki örnek, belirtilen aralıktaki tek sayıları numaralandıran bir yöntemi tanımlar OddSequence
. Numaralandırıcı yöntemine 100'den büyük bir sayı geçirdiğinden OddSequence
, yöntemi bir ArgumentOutOfRangeExceptionoluşturur. Örnekteki çıktıda gösterildiği gibi, özel durum yalnızca sayıları yinelediğinizde ortaya çıkar ve numaralandırıcıyı aldığınızda gösterilmez.
public class IteratorWithoutLocalExample
{
public static void Main()
{
IEnumerable<int> xs = OddSequence(50, 110);
Console.WriteLine("Retrieved enumerator...");
foreach (var x in xs) // line 11
{
Console.Write($"{x} ");
}
}
public static IEnumerable<int> OddSequence(int start, int end)
{
if (start < 0 || start > 99)
throw new ArgumentOutOfRangeException(nameof(start), "start must be between 0 and 99.");
if (end > 100)
throw new ArgumentOutOfRangeException(nameof(end), "end must be less than or equal to 100.");
if (start >= end)
throw new ArgumentException("start must be less than end.");
for (int i = start; i <= end; i++)
{
if (i % 2 == 1)
yield return i;
}
}
}
// The example displays the output like this:
//
// Retrieved enumerator...
// Unhandled exception. System.ArgumentOutOfRangeException: end must be less than or equal to 100. (Parameter 'end')
// at IteratorWithoutLocalExample.OddSequence(Int32 start, Int32 end)+MoveNext() in IteratorWithoutLocal.cs:line 22
// at IteratorWithoutLocalExample.Main() in IteratorWithoutLocal.cs:line 11
Yineleyici mantığını yerel bir işleve koyarsanız, aşağıdaki örnekte gösterildiği gibi numaralandırıcıyı aldığınızda bağımsız değişken doğrulama özel durumları oluşturulur:
public class IteratorWithLocalExample
{
public static void Main()
{
IEnumerable<int> xs = OddSequence(50, 110); // line 8
Console.WriteLine("Retrieved enumerator...");
foreach (var x in xs)
{
Console.Write($"{x} ");
}
}
public static IEnumerable<int> OddSequence(int start, int end)
{
if (start < 0 || start > 99)
throw new ArgumentOutOfRangeException(nameof(start), "start must be between 0 and 99.");
if (end > 100)
throw new ArgumentOutOfRangeException(nameof(end), "end must be less than or equal to 100.");
if (start >= end)
throw new ArgumentException("start must be less than end.");
return GetOddSequenceEnumerator();
IEnumerable<int> GetOddSequenceEnumerator()
{
for (int i = start; i <= end; i++)
{
if (i % 2 == 1)
yield return i;
}
}
}
}
// The example displays the output like this:
//
// Unhandled exception. System.ArgumentOutOfRangeException: end must be less than or equal to 100. (Parameter 'end')
// at IteratorWithLocalExample.OddSequence(Int32 start, Int32 end) in IteratorWithLocal.cs:line 22
// at IteratorWithLocalExample.Main() in IteratorWithLocal.cs:line 8
Yerel işlevler ile lambda ifadeleri karşılaştırması
İlk bakışta yerel işlevler ve lambda ifadeleri birbirine çok benzer. Çoğu durumda lambda ifadelerini ve yerel işlevleri kullanma arasında seçim stil ve kişisel tercih konusudur. Ancak, farkında olmanız gereken birini veya diğerini kullanabileceğiniz gerçek farklılıklar vardır.
Şimdi faktöriyel algoritmanın yerel işlevi ile lambda ifadesi uygulamaları arasındaki farkları inceleyelim. Yerel işlev kullanan sürüm şu şekildedir:
public static int LocalFunctionFactorial(int n)
{
return nthFactorial(n);
int nthFactorial(int number) => number < 2
? 1
: number * nthFactorial(number - 1);
}
Bu sürümde lambda ifadeleri kullanılır:
public static int LambdaFactorial(int n)
{
Func<int, int> nthFactorial = default(Func<int, int>);
nthFactorial = number => number < 2
? 1
: number * nthFactorial(number - 1);
return nthFactorial(n);
}
Adlandırma
Yerel işlevler açıkça yöntemler gibi adlandırılır. Lambda ifadeleri anonim yöntemlerdir ve genellikle Action
veya Func
türlerinde bir delegate
türün değişkenlerine atanmalıdır. Yerel bir işlev bildirdiğinizde, işlem normal bir yöntem yazmaya benzer; bir dönüş türü ve işlev imzası bildirirsiniz.
İşlev imzaları ve lambda ifade türleri
Lambda ifadeleri, bağımsız değişkeni ve dönüş türlerini belirlemek için atandıkları değişkenin türüne Action
/Func
bağlıdır. Yerel işlevlerde, söz dizimi normal bir yöntem yazmaya çok benzer olduğundan, bağımsız değişken türleri ve dönüş türü zaten işlev bildiriminin bir parçasıdır.
C# 10 ile başlayarak, bazı lambda ifadeleri doğal bir türe sahiptir ve bu da derleyicinin lambda ifadesinin dönüş türünü ve parametre türlerini çıkartır.
Kesin atama
Lambda ifadeleri, çalışma zamanında bildirilen ve atanan nesnelerdir. Bir lambda ifadesinin kullanılabilmesi için kesinlikle atanması gerekir: Action
/Func
atanacağı değişken bildirilmeli ve lambda ifadesi buna atanmış olmalıdır. LambdaFactorial
Tanımlamadan önce lambda ifadesini nthFactorial
bildirmesi ve başlatması gerektiğine dikkat edin. Bunun yapılmaması, atamadan önce başvuru nthFactorial
için derleme süresi hatasına neden olur.
Yerel işlevler derleme zamanında tanımlanır. Değişkenlere atanmadığından, kapsam dahilindeki herhangi bir kod konumundan bunlara başvurulabilir; ilk örneğimizdeLocalFunctionFactorial
, yerel işlevimizi deyimin return
üstünde veya altında bildirebilir ve derleyici hatalarını tetiklemeyiz.
Bu farklılıklar özyinelemeli algoritmaların yerel işlevler kullanılarak oluşturulmasının daha kolay olduğu anlamına gelir. Kendisini çağıran bir yerel işlev bildirebilir ve tanımlayabilirsiniz. Lambda ifadeleri, aynı lambda ifadesine başvuran bir gövdeye yeniden atanmadan önce bildirilmeli ve varsayılan bir değer atanmalıdır.
Temsilci olarak uygulama
Lambda ifadeleri, bildirildiğinde temsilcilere dönüştürülür. Yerel işlevler, geleneksel bir yöntem veya temsilci olarak yazılabilmesi için daha esnektir. Yerel işlevler yalnızca temsilci olarak kullanıldığında temsilcilere dönüştürülür.
Yerel bir işlev bildirir ve buna yalnızca bir yöntem gibi çağırarak başvurursanız, temsilciye dönüştürülmeyecektir.
Değişken yakalama
Kesin atama kuralları, yerel işlev veya lambda ifadesi tarafından yakalanan tüm değişkenleri de etkiler. Derleyici, yerel işlevlerin kapsayan kapsamda yakalanan değişkenleri kesinlikle atamasını sağlayan statik analiz gerçekleştirebilir. Bu örneği ele alalım:
int M()
{
int y;
LocalFunction();
return y;
void LocalFunction() => y = 0;
}
Derleyici, çağrıldığında bunun kesinlikle atadığını LocalFunction
y
belirleyebilir. Çünkü LocalFunction
deyiminden return
önce çağrılır, y
kesinlikle deyiminde return
atanır.
Yerel bir işlev kapsayan kapsamdaki değişkenleri yakaladığında, yerel işlevin temsilci türleri gibi bir kapatma kullanılarak uygulandığını unutmayın.
Yığın ayırmaları
Kullanımlarına bağlı olarak, yerel işlevler lambda ifadeleri için her zaman gerekli olan yığın ayırmalarını önleyebilir. Yerel işlev hiçbir zaman temsilciye dönüştürülmezse ve yerel işlev tarafından yakalanan değişkenlerin hiçbiri temsilcilere dönüştürülen diğer lambda'lar veya yerel işlevler tarafından yakalanmazsa, derleyici yığın ayırmalarını önleyebilir.
Bu zaman uyumsuz örneği göz önünde bulundurun:
public async Task<string> PerformLongRunningWorkLambda(string address, int index, string name)
{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required", paramName: nameof(address));
if (index < 0)
throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));
Func<Task<string>> longRunningWorkImplementation = async () =>
{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}. Enjoy.";
};
return await longRunningWorkImplementation();
}
Bu lambda ifadesinin kapanışı address
, index
ve name
değişkenlerini içerir. Yerel işlevler söz konusu olduğunda, kapatmayı uygulayan nesne bir struct
tür olabilir. Bu yapı türü, yerel işleve başvuruyla geçirilir. Uygulamadaki bu fark, ayırmada tasarruf sağlar.
Lambda ifadeleri için gereken örnekleme, zaman açısından kritik kod yollarında performans faktörü olabilecek ek bellek ayırmaları anlamına gelir. Yerel işlevler bu ek yüke neden olmaz. Yukarıdaki örnekte, yerel işlevler sürümünün lambda ifade sürümünden iki daha az ayırması vardır.
Yerel işlevinizin temsilciye dönüştürülmeyeceğini ve bu işlev tarafından yakalanan değişkenlerin hiçbirinin temsilcilere dönüştürülen diğer lambda'lar veya yerel işlevler tarafından yakalanmadığını biliyorsanız, yerel işlevinizin yerel işlev olarak static
bildirerek yığında ayrılmasını önlediğini garanti edebilirsiniz.
İpucu
Yerel işlevlerin her zaman işaretlendiğinden static
emin olmak için .NET kod stili kuralı IDE0062 etkinleştirin.
Not
Bu yöntemin yerel işlev eşdeğeri de kapanış için bir sınıf kullanır. Yerel bir işlevin kapatılmasının bir veya struct
olarak class
uygulanıp uygulanmadığı bir uygulama ayrıntısıdır. Yerel bir işlev bir struct
kullanabilirken, lambda her zaman bir class
kullanır.
public async Task<string> PerformLongRunningWork(string address, int index, string name)
{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required", paramName: nameof(address));
if (index < 0)
throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));
return await longRunningWorkImplementation();
async Task<string> longRunningWorkImplementation()
{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}. Enjoy.";
}
}
Anahtar sözcüğün yield
kullanımı
Bu örnekte gösterilmeyen son bir avantaj, yerel işlevlerin bir değer dizisi oluşturmak için söz dizimi kullanılarak yield return
yineleyici olarak uygulanabilmesidir.
public IEnumerable<string> SequenceToLowercase(IEnumerable<string> input)
{
if (!input.Any())
{
throw new ArgumentException("There are no items to convert to lowercase.");
}
return LowercaseIterator();
IEnumerable<string> LowercaseIterator()
{
foreach (var output in input.Select(item => item.ToLower()))
{
yield return output;
}
}
}
yield return
Lambda ifadelerinde deyimine izin verilmez. Daha fazla bilgi için bkz. derleyici hatası CS1621.
Yerel işlevler lambda ifadelerine yedekli gibi görünse de, aslında farklı amaçlara hizmet eder ve farklı kullanımlara sahiptir. Yalnızca başka bir yöntemin bağlamından çağrılan bir işlev yazmak istediğinizde, yerel işlevler büyük/küçük harf için daha verimlidir.
C# dili belirtimi
Daha fazla bilgi için C# dil belirtiminin Yerel işlev bildirimleri bölümüne bakın.