Helyi függvények (C# programozási útmutató)
A helyi függvények olyan típusú metódusok, amelyek egy másik tagba vannak ágyazva. Csak a tagjuktól hívhatók meg. A helyi függvények deklarálhatók és meghívhatók a következőből:
- Metódusok, különösen iterátor- és aszinkron metódusok
- Konstruktorok
- Tulajdonságkiegészítők
- Eseménykiegészítők
- Névtelen metódusok
- Lambda-kifejezések
- Döntősök
- Egyéb helyi függvények
A helyi függvények azonban nem deklarálhatók egy kifejezés által testesített tagon belül.
Feljegyzés
Bizonyos esetekben lambda kifejezéssel implementálhatja a helyi függvények által is támogatott funkciókat. Összehasonlításért tekintse meg a Helyi függvények és a lambda kifejezések című témakört.
A helyi függvények egyértelművé teszik a kód szándékát. A kód olvasói láthatják, hogy a metódus nem hívható meg, kivéve a kódot tartalmazó metódust. A csapatprojektek esetében azt is lehetetlenné teszik, hogy egy másik fejlesztő tévedésből közvetlenül az osztály vagy a strustruktúra más helyről hívja meg a metódust.
Helyi függvény szintaxisa
A helyi függvények beágyazott metódusként vannak definiálva egy tagon belül. Definíciója a következő szintaxist tartalmazza:
<modifiers> <return-type> <method-name> <parameter-list>
Feljegyzés
A <parameter-list>
paraméter nem tartalmazza a környezeti kulcsszóval value
elnevezett paramétereket.
A fordító létrehozza az "érték" ideiglenes változót, amely tartalmazza a hivatkozott külső változókat, ami később kétértelműséget okoz, és váratlan viselkedést is okozhat.
A következő módosítókat használhatja helyi függvényekkel:
async
unsafe
static
A statikus helyi függvények nem tudnak helyi változókat vagy példányállapotokat rögzíteni.extern
Külső helyi függvénynek kell lenniestatic
.
Az adott tagban definiált helyi változók, beleértve a metódusparamétereket is, nem statikus helyi függvényekben érhetők el.
A metódusdefiníciókkal ellentétben a helyi függvénydefiníciók nem tartalmazhatják a taghozzáférés-módosítót. Mivel minden helyi függvény privát, beleértve egy hozzáférés-módosítót, például a private
kulcsszót, a CS0106 fordítási hibát generálja, "A módosító "private" érvénytelen ehhez az elemhez."
Az alábbi példa egy olyan helyi függvényt AppendPathSeparator
határoz meg, amely egy nevesített GetText
metódushoz tartozik:
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 + @"\";
}
}
Attribútumokat alkalmazhat egy helyi függvényre, annak paramétereire és típusparaméterekre, ahogy az alábbi példa mutatja:
#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;
}
}
Az előző példa egy speciális attribútummal segíti a fordítót a null értékű környezetben történő statikus elemzésben.
Helyi függvények és kivételek
A helyi függvények egyik hasznos funkciója, hogy lehetővé teszik a kivételek azonnali felszínre hozását. Az iterátor metódusok esetében a kivételek csak akkor jelennek meg, ha a visszaadott sorozat számba van adva, és nem az iterátor lekérésekor. Az aszinkron metódusok esetében az aszinkron metódusban alkalmazott kivételek akkor figyelhetők meg, amikor a visszaadott feladatra várni kell.
Az alábbi példa egy metódust OddSequence
határoz meg, amely egy megadott tartományban számba adja a páratlan számokat. Mivel 100-nál nagyobb számot ad át az OddSequence
enumerátor metódusnak, a metódus egy ArgumentOutOfRangeException. Ahogy a példa kimenete is mutatja, a kivétel csak akkor jelenik meg, ha iterálja a számokat, és nem az enumerátor lekérésekor.
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
Ha iterátorlogikát helyez egy helyi függvénybe, az argumentumérvényesítési kivételek az enumerátor lekérésekor jelennek meg, ahogy az alábbi példa mutatja:
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
Helyi függvények és lambdakifejezések
Első pillantásra a helyi függvények és a lambda kifejezések nagyon hasonlóak . Sok esetben a lambda kifejezések és a helyi függvények használata közötti választás a stílus és a személyes preferencia kérdése. Vannak azonban valós különbségek abban, hogy hol használhatja az egyiket vagy a másikat, amellyel tisztában kell lennie.
Vizsgáljuk meg a helyi függvény és a lambda kifejezés implementációi közötti különbségeket a faktoriális algoritmusban. A helyi függvényt használó verzió:
public static int LocalFunctionFactorial(int n)
{
return nthFactorial(n);
int nthFactorial(int number) => number < 2
? 1
: number * nthFactorial(number - 1);
}
Ez a verzió lambdakifejezéseket használ:
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);
}
Elnevezés
A helyi függvények neve kifejezetten metódusként van elnevezve. A Lambda-kifejezések névtelen metódusok, és egy delegate
típus változóihoz kell hozzárendelni, általában vagy Action
Func
típusonként. Ha helyi függvényt deklarál, a folyamat olyan, mint egy normál metódus írása; deklarál egy visszatérési típust és egy függvény-aláírást.
Függvény-aláírások és lambda-kifejezéstípusok
A Lambda-kifejezések a hozzárendelt változó típusára Action
/Func
támaszkodnak az argumentumok és a visszatérési típusok meghatározásához. A helyi függvényekben, mivel a szintaxis hasonlít egy normál metódus írásához, az argumentumtípusok és a visszatérési típus már a függvénydeklaráció részét képezik.
A C# 10-től kezdődően egyes lambdakifejezések természetes típusúak, így a fordító a lambda kifejezés visszatérési típusát és paramétertípusait is kikövetkezheti.
Határozott hozzárendelés
A Lambda-kifejezések olyan objektumok, amelyek futásidőben deklarálva és hozzárendelve vannak. Ahhoz, hogy egy lambda kifejezést használhasson, határozottan hozzá kell rendelni: a Action
/Func
hozzárendelni kívánt változót deklarálni kell, és hozzá kell rendelni a lambda kifejezést. Figyelje meg, hogy LambdaFactorial
definiálása előtt deklarálnia és inicializálnia kell a lambda kifejezést nthFactorial
. Ha ezt nem teszi meg, fordítási időhiba lép fel a hivatkozáskor nthFactorial
a hozzárendelés előtt.
A helyi függvények fordítási időben vannak definiálva. Mivel nincsenek változókhoz rendelve, bármely kódhelyről hivatkozhatunk rájuk, ahol a hatókörben van. Az első példában LocalFunctionFactorial
deklarálhatjuk a helyi függvényt az return
utasítás felett vagy alatt, és nem válthatunk ki fordítóhibákat.
Ezek a különbségek azt jelentik, hogy a rekurzív algoritmusok könnyebben hozhatók létre helyi függvények használatával. Deklarálhat és definiálhat egy helyi függvényt, amely meghívja magát. A Lambda-kifejezéseket deklarálni kell, és hozzá kell rendelni egy alapértelmezett értéket, mielőtt újra hozzárendelhetők lennének egy olyan törzshez, amely ugyanarra a lambda kifejezésre hivatkozik.
Megvalósítás meghatalmazottként
A Lambda-kifejezések deklarálásakor a rendszer meghatalmazottakká alakítja őket. A helyi függvények rugalmasabbak, mivel hagyományos módszerként vagy meghatalmazottként is írhatók. A helyi függvények csak meghatalmazottként való használat esetén lesznek delegáltakká alakítva .
Ha deklarál egy helyi függvényt, és csak metódusként hívja meg, a rendszer nem konvertálja delegálttá.
Változó rögzítése
A határozott hozzárendelés szabályai a helyi függvény vagy lambda kifejezés által rögzített változókat is érintik. A fordító képes statikus elemzést végezni, amely lehetővé teszi, hogy a helyi függvények biztosan hozzárendeljenek rögzített változókat a belefoglaló hatókörbe. Vegye fontolóra ezt a példát:
int M()
{
int y;
LocalFunction();
return y;
void LocalFunction() => y = 0;
}
A fordító meghatározhatja, hogy a LocalFunction
híváskor biztosan hozzárendeli-e y
. Mivel LocalFunction
az return
utasítás előtt van meghívva, y
a rendszer mindenképpen hozzá van rendelve az return
utasításhoz.
Vegye figyelembe, hogy ha egy helyi függvény rögzíti a változókat a belefoglaló hatókörbe, a helyi függvény egy lezárással lesz implementálva, például a delegálási típusok.
Halomfoglalások
Használatuktól függően a helyi függvények elkerülhetik a lambdakifejezésekhez mindig szükséges halomfoglalásokat. Ha a helyi függvények soha nem lesznek delegáltak, és a helyi függvény által rögzített változók egyikét sem rögzítik más lambdák vagy helyi függvények, amelyeket delegáltakká konvertálnak, a fordító elkerülheti a halomfoglalásokat.
Tekintse meg ezt az aszinkron példát:
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();
}
A lambda kifejezés bezárása tartalmazza a , index
és name
a address
változókat. Helyi függvények esetén a lezárást megvalósító objektum típus lehet struct
. Ezt a szerkezettípust a rendszer a helyi függvényre hivatkozva adja át. Ez a megvalósítási különbség megtakarítana egy foglalást.
A lambda-kifejezésekhez szükséges példányosítás további memóriafoglalásokat jelent, ami teljesítménytényező lehet az idő szempontjából kritikus kódútvonalakban. A helyi függvények nem járnak ezzel a többletterheléssel. A fenti példában a helyi függvények verziója két kevesebb foglalással rendelkezik, mint a lambda kifejezés verziója.
Ha tudja, hogy a helyi függvény nem lesz delegálttá alakítva, és az általa rögzített változók egyikét sem rögzíti más lambdas vagy helyi függvény, amelyeket delegáltakká alakít, akkor garantálhatja, hogy a helyi függvény ne legyen lefoglalva a halomra, ha deklarálja azt helyi függvényként static
.
Tipp.
Engedélyezze a .NET-kódstílus-szabály IDE0062 , hogy a helyi függvények mindig meg legyenek jelölve static
.
Feljegyzés
A metódus helyi függvénye egy osztályt is használ a lezáráshoz. Azt, hogy egy helyi függvény bezárása implementálva class
van-e, vagy implementálási struct
részlet. A helyi függvények használhatnak egy struct
, míg a lambda mindig egy 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.";
}
}
A yield
kulcsszó használata
A mintában nem bemutatott végső előny az, hogy a helyi függvények iterátorként implementálhatók, a yield return
szintaxis használatával pedig értéksorozatot hozhatnak létre.
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;
}
}
}
Az yield return
utasítás nem engedélyezett a lambda kifejezésekben. További információ: CS1621 fordítói hiba.
Bár a helyi függvények redundánsnak tűnhetnek a lambda kifejezésekre, valójában különböző célokat szolgálnak, és különböző felhasználási módokkal rendelkeznek. A helyi függvények hatékonyabbak abban az esetben, ha olyan függvényt szeretne írni, amelyet csak egy másik metódus környezetéből hívunk meg.
C# nyelvspecifikáció
További információ: A C# nyelv specifikációjának Helyi függvénydeklarációk szakasza.
Lásd még
Visszajelzés
https://aka.ms/ContentUserFeedback.
Hamarosan elérhető: 2024-ben fokozatosan kivezetjük a GitHub-problémákat a tartalom visszajelzési mechanizmusaként, és lecseréljük egy új visszajelzési rendszerre. További információ:Visszajelzés küldése és megtekintése a következőhöz: