Yerel işlevler (C# Programlama Kılavuzu)
C# 7,0 ' den başlayarak, c# Yerel Işlevleri destekler. Yerel işlevler, başka bir üyede iç içe yerleştirilmiş bir türün özel yöntemleridir. Yalnızca kendi kapsayıcı üyelerinden çağrılabilir. Yerel işlevler içinde bildirilebilecek ve şuradan çağrılabilir:
- Yöntemler, özellikle Yineleyici yöntemleri 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-Bodied üyesi içinde bildirilemez.
Not
Bazı durumlarda, bir yerel işlev tarafından desteklenen işlevselliği uygulamak için bir lambda ifadesi kullanabilirsiniz. Bir karşılaştırma için bkz . yerel işlevler ve lambda ifadeleri.
Yerel işlevler, kodunuzun amacını açık hale getirir. Kodunuzu okuyan herkes, yöntemin kapsayan Yöntem dışında çağrılabilir olmadığını görebilir. Ekip projeleri için, başka bir geliştiricinin yöntemi doğrudan sınıf veya yapı içinde başka bir yerde çağırmak olanaksız hale getirir.
Yerel işlev sözdizimi
Yerel bir işlev, kapsayan bir üye içinde iç içe geçmiş bir yöntem olarak tanımlanır. Tanımı aşağıdaki sözdizimine sahiptir:
<modifiers> <return-type> <method-name> <parameter-list>
Aşağıdaki değiştiricileri yerel bir işlevle kullanabilirsiniz:
asyncunsafestatic(C# 8,0 ve üzeri sürümlerde). Statik bir yerel işlev yerel değişkenler veya örnek durumu yakalayamaz.extern(C# 9,0 ve üzeri sürümlerde). Dış yerel işlev olmalıdırstatic.
Yöntem parametreleri de dahil olmak üzere, kapsayan üyede tanımlanan tüm yerel değişkenlere statik olmayan bir yerel işlevde erişilebilir.
Bir yöntem tanımının aksine, yerel bir işlev tanımı üye erişim değiştiricisini içeremez. Tüm yerel işlevler özel olduğundan, anahtar sözcüğü gibi bir erişim değiştiricisi de dahil olmak üzere, private "özel ' değiştiricisi Bu öğe için geçerli değil."
Aşağıdaki örnek adlı bir yerel işlevi tanımlar AppendPathSeparator GetText :
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 + @"\";
}
}
C# 9,0 ' den başlayarak, aşağıdaki örnekte gösterildiği gibi, bir yerel işleve, parametreleri 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;
}
}
Önceki örnek, null olabilen bir bağlamda statik çözümlemede derleyiciye yardım etmek için özel bir öznitelik kullanır.
Yerel işlevler ve özel durumlar
Yerel işlevlerin yararlı özelliklerinden biri, özel durumların hemen yüzeyine izin verebilir. Yöntem yineleyiciler için, özel durumlar yalnızca döndürülen dizi numaralandırıldıktan sonra, yineleyici alındığında değil, ortaya çıkacak. Zaman uyumsuz metotlar için, bir zaman uyumsuz yöntemde oluşturulan özel durumlar, döndürülen görev beklendiğinde gözlemlenir.
Aşağıdaki örnek, OddSequence belirtilen aralıktaki tek sayıları numaralandırır bir yöntemi tanımlar. Numaralandırıcı yöntemine 100 ' den büyük bir sayı geçirdiğinden OddSequence , yöntemi bir oluşturur ArgumentOutOfRangeException . Örneğin çıkışının gösterdiği gibi, özel durum yalnızca sayıları tekrarladığı zaman, numaralandırıcıyı alırken değil, yüzeyleri.
using System;
using System.Collections.Generic;
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 yerleştirirseniz, aşağıdaki örnekte gösterildiği gibi, Numaralandırıcı aldığınızda bağımsız değişken doğrulama özel durumları atılır.
using System;
using System.Collections.Generic;
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 ve lambda ifadeleri karşılaştırması
İlk bakışta, yerel işlevler ve lambda ifadeleri çok benzerdir. Birçok durumda, lambda ifadeleri ve yerel işlevler kullanma arasında seçim stili ve kişisel tercihlerden bağımsız olur. Ancak, dikkat etmeniz gereken bir veya diğerini kullanabileceğiniz gerçek farklılıklar vardır.
Bu, yerel işlev ve çarpınımı algoritmasının lambda ifadesi uygulamaları arasındaki farkları inceleyelim. Yerel bir işlev kullanan sürümü aşağıda verilmiştir:
public static int LocalFunctionFactorial(int n)
{
return nthFactorial(n);
int nthFactorial(int number) => number < 2
? 1
: number * nthFactorial(number - 1);
}
Bu sürüm lambda ifadeleri kullanı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, yöntemler gibi açıkça adlandırılır. Lambda ifadeleri anonim yöntemlerdir ve delegate genellikle veya türler türünde bir türün değişkenlerine atanması gerekir Action Func . Yerel bir işlev bildirdiğinizde, işlem normal bir yöntem yazmak gibidir; bir dönüş türü ve bir işlev imzası bildirirsiniz.
İşlev imzaları ve lambda ifadesi türleri
Lambda ifadeleri, Action / Func bağımsız değişkenini ve dönüş türlerini belirlemekte atanan değişkenin türüne bağlıdır. Yerel işlevlerde, söz dizimi normal bir yöntemi yazarken ç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 ' dan itibaren, bazı lambda ifadeleri doğal bir türe sahiptir ve bu da derleyicinin lambda ifadesinin dönüş türünü ve parametre türlerini çıkarması sağlar.
Kesin atama
Lambda ifadeleri, çalışma zamanında belirtilen ve atanan nesnelerdir. Bir lambda ifadesinin kullanılabilmesi için, kesinlikle atanması gerekir: kendisine Action / Func atanacak olan değişken bildirilmelidir ve kendisine atanmış lambda ifadesi. LambdaFactorialTanımlanmadan önce lambda ifadesini bildirmelidir ve başlatmalıdır nthFactorial . Bu nedenle, atamadan önce başvurmak için derleme zamanı hatasına neden nthFactorial olur.
Yerel işlevler, derleme zamanında tanımlanır. Değişkenlere atanmadığından, bunlara kapsamda olduğu herhangi bir kod konumundan başvurulabilirler; ilk örneğimizde LocalFunctionFactorial , deyimin üzerinde veya altında return olan ve herhangi bir derleyici hatası tetiklemeyen yerel işlevimizi bildirebiliriz.
Bu farklılıklar özyinelemeli algoritmaların yerel işlevler kullanılarak oluşturulması daha kolay hale gelir. Kendisini çağıran bir yerel işlev tanımlayabilir ve tanımlayabilirsiniz. Lambda ifadeleri, aynı lambda ifadesine başvuran bir gövdeye yeniden atanabilmeleri için bildirilmelidir ve varsayılan bir değer atanmalıdır.
Temsilci olarak uygulama
Lambda ifadeleri, bildirildiği zaman temsilcilere dönüştürülür. Yerel işlevler geleneksel bir yöntem veya temsilci olarak yazabildiği 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 bildirirseniz ve yalnızca bir yöntem gibi çağırarak buna başvurmanız durumunda, bir temsilciye dönüştürülmez.
Değişken yakalama
Kesin atamanın kuralları, yerel işlev veya lambda ifadesi tarafından yakalanan tüm değişkenleri de etkiler. Derleyici, yerel işlevlerin kapsayan kapsamda kesin olarak yakalanan değişkenleri atamasını sağlayan statik analizler gerçekleştirebilir. Bu örneği ele alalım:
int M()
{
int y;
LocalFunction();
return y;
void LocalFunction() => y = 0;
}
Derleyici, LocalFunction çağrıldığında kesinlikle atayıp atamayacağını tespit edebilir y . , LocalFunction Deyimden önce çağrıldığı için return , y ifadeye kesin olarak atanır return .
Yerel bir işlev kapsayan kapsamdaki değişkenleri yakaladığında, yerel işlevin temsilci türü olarak uygulandığını unutmayın.
Yığın ayırmaları
Yerel işlevler, kullanım amaçlarına bağlı olarak, lambda ifadeleri için her zaman gerekli olan yığın ayırmalara engel olabilir. Yerel bir işlev hiçbir şekilde bir temsilciye dönüştürülürse ve yerel işlev tarafından yakalanan değişkenlerden hiçbiri, başka Lambdalar veya temsilcilere dönüştürülen yerel işlevler tarafından yakalanmazsa, derleyici yığın ayırmalara engel olabilir.
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, kapanışı uygulayan nesne bir struct tür olabilir. Bu yapı türü yerel işleve başvuruya göre geçirilir. Uygulamadaki bu fark bir ayırmaya kaydedilir.
Lambda ifadeleri için gereken örnek oluşturma, zaman açısından kritik kod yollarındaki bir performans faktörü olabilecek fazladan bellek ayırmaları anlamına gelir. Yerel işlevler bu ek yüke neden olmaz. Yukarıdaki örnekte, yerel işlevlerin sürümünde lambda ifadesi sürümünden daha az sayıda ayırma vardır.
Yerel işlevinizin bir temsilciye dönüştürülemediğini ve tarafından yakalanan değişkenlerden hiçbirinin, temsilcilere dönüştürülen başka Lambdalar veya yerel işlevler tarafından yakalanıp yakalanmayacağını biliyorsanız, yerel işlevinizin yığın üzerinde yerel bir işlev olarak bildirerek ayrılmasının önleneceğini garanti edebilirsiniz static . Bu özelliğin C# 8,0 ve daha yeni sürümlerde kullanılabilir olduğunu unutmayın.
Not
Bu yöntemin yerel işlev eşdeğeri, kapanış için bir sınıf de kullanır. Yerel bir işlev için Kapanışın bir veya olarak uygulanıp uygulanmadığı bir class struct uygulama ayrıntısı. Yerel bir işlev bir kullanabilir, struct ancak bir lambda her zaman bir kullanır class .
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.";
}
}
yieldAnahtar sözcüğünün kullanımı
Bu örnekte gösterilmeyen bir son avantaj, yerel işlevlerin yineleyiciler olarak uygulanabilmesinin yanı yield return sıra bir değer dizisi oluşturmak için söz dizimini kullanmaktır.
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 returnLambda ifadelerinde ifadeye izin verilmiyor, bkz. DERLEYICI hatası CS1621.
Yerel işlevler Lambda ifadelerinde gereksiz gibi görünse de, bu işlemler aslında farklı amaçlara hizmet eder ve farklı kullanımlar sağlar. Yalnızca başka bir yöntemin bağlamından çağrılan bir işlev yazmak istediğinizde yerel işlevler bu durum için daha etkilidir.