Funkcje lokalne (C# Przewodnik programowania)Local functions (C# Programming Guide)

Począwszy od C# 7,0, C# obsługuje funkcje lokalne.Starting with C# 7.0, C# supports local functions. Funkcje lokalne są prywatnymi metodami typu, które są zagnieżdżone w innym elemencie członkowskim.Local functions are private methods of a type that are nested in another member. Mogą być wywoływane tylko z ich składowych.They can only be called from their containing member. Funkcje lokalne można zadeklarować w i wywołać z:Local functions can be declared in and called from:

  • Metody, zwłaszcza metody iteratorów i metody asynchroniczneMethods, especially iterator methods and async methods
  • KonstruktorzyConstructors
  • Metody dostępu do właściwościProperty accessors
  • Metody dostępu zdarzeńEvent accessors
  • Metody anonimoweAnonymous methods
  • Wyrażenia lambdaLambda expressions
  • FinalizatoryFinalizers
  • Inne funkcje lokalneOther local functions

Jednak funkcji lokalnych nie można deklarować wewnątrz elementu członkowskiego będącego w posiadaniu wyrażenia.However, local functions can't be declared inside an expression-bodied member.

Uwaga

W niektórych przypadkach można użyć wyrażenia lambda, aby zaimplementować funkcje również obsługiwane przez funkcję lokalną.In some cases, you can use a lambda expression to implement functionality also supported by a local function. Aby zapoznać się z porównaniem, zobacz funkcje lokalne w porównaniu z wyrażeniami lambda.For a comparison, see Local functions compared to Lambda expressions.

Funkcje lokalne sprawiają, że zamiar kodu jest przejrzysty.Local functions make the intent of your code clear. Każda osoba odczytująca kod może zobaczyć, że metoda nie jest wywoływana z wyjątkiem metody zawierającej.Anyone reading your code can see that the method is not callable except by the containing method. W przypadku projektów zespołowych uniemożliwiają one również innym deweloperom błędne wywoływanie metody bezpośrednio z innych miejsc w klasie lub strukturze.For team projects, they also make it impossible for another developer to mistakenly call the method directly from elsewhere in the class or struct.

Składnia funkcji lokalnychLocal function syntax

Funkcja lokalna jest definiowana jako metoda zagnieżdżona wewnątrz składowej zawierającej.A local function is defined as a nested method inside a containing member. Jego definicja ma następującą składnię:Its definition has the following syntax:

<modifiers: async | unsafe> <return-type> <method-name> <parameter-list>

Funkcje lokalne mogą używać modyfikatorów Async i UNSAFE .Local functions can use the async and unsafe modifiers.

Należy zauważyć, że wszystkie zmienne lokalne, które są zdefiniowane w składowej zawierającej, łącznie z parametrami metody, są dostępne w funkcji lokalnej.Note that all local variables that are defined in the containing member, including its method parameters, are accessible in the local function.

W przeciwieństwie do definicji metody lokalnej definicja funkcji nie może zawierać modyfikatora dostępu do składowej.Unlike a method definition, a local function definition cannot include the member access modifier. Ponieważ wszystkie funkcje lokalne są prywatne, w tym modyfikator dostępu, taki jak private słowo kluczowe, generuje błąd kompilatora CS0106 "modyfikator" Private "jest nieprawidłowy dla tego elementu".Because all local functions are private, including an access modifier, such as the private keyword, generates compiler error CS0106, "The modifier 'private' is not valid for this item."

Uwaga

Przed C# 8,0 funkcja lokalna nie może zawierać modyfikatora static.Prior to C# 8.0, local functions cannot include the static modifier. W tym static słowo kluczowe generuje błąd kompilatora CS0106, "modyfikator" static "jest nieprawidłowy dla tego elementu".Including the static keyword generates compiler error CS0106, "The modifier 'static' is not valid for this item."

Ponadto atrybuty nie mogą być stosowane do funkcji lokalnej ani do jej parametrów i parametrów typu.In addition, attributes can't be applied to the local function or to its parameters and type parameters.

W poniższym przykładzie zdefiniowano funkcję lokalną o nazwie AppendPathSeparator, która jest prywatna dla metody o nazwie GetText:The following example defines a local function named AppendPathSeparator that is private to a method named GetText:

using System;
using System.IO;

class Example
{
    static void Main()
    {
        string contents = GetText(@"C:\temp", "example.txt");
        Console.WriteLine("Contents of the file:\n" + contents);
    }
   
    private static string GetText(string path, string filename)
    {
         var sr = File.OpenText(AppendPathSeparator(path) + filename);
         var text = sr.ReadToEnd();
         return text;
         
         // Declare a local function.
         string AppendPathSeparator(string filepath)
         {
            if (! filepath.EndsWith(@"\"))
               filepath += @"\";

            return filepath;   
         }
    } 
}

Lokalne funkcje i wyjątkiLocal functions and exceptions

Jedną z użytecznych funkcji lokalnych funkcji jest możliwość natychmiastowego zezwolenia na korzystanie z wyjątków.One of the useful features of local functions is that they can allow exceptions to surface immediately. W przypadku iteratorów metod wyjątki są nakierowane tylko wtedy, gdy zwracana sekwencja jest wyliczana, a nie podczas pobierania iteratora.For method iterators, exceptions are surfaced only when the returned sequence is enumerated, and not when the iterator is retrieved. W przypadku metod asynchronicznych wszystkie wyjątki zgłoszone w metodzie asynchronicznej są zaobserwowane, gdy zwracane zadanie jest oczekiwane.For async methods, any exceptions thrown in an async method are observed when the returned task is awaited.

W poniższym przykładzie zdefiniowano metodę OddSequence, która wylicza nieparzyste liczby między określonym zakresem.The following example defines an OddSequence method that enumerates odd numbers between a specified range. Ponieważ przekazuje liczbę większą niż 100 do metody modułu wyliczającego OddSequence Metoda generuje ArgumentOutOfRangeException.Because it passes a number greater than 100 to the OddSequence enumerator method, the method throws an ArgumentOutOfRangeException. Ponieważ dane wyjściowe z przykładu pokazują, powierzchnie wyjątków tylko w przypadku iteracji liczby, a nie podczas pobierania modułu wyliczającego.As the output from the example shows, the exception surfaces only when you iterate the numbers, and not when you retrieve the enumerator.

using System;
using System.Collections.Generic;

class Example
{
   static void Main()
   {
      IEnumerable<int> ienum = OddSequence(50, 110);
      Console.WriteLine("Retrieved enumerator...");
      
      foreach (var i in ienum)
      {
         Console.Write($"{i} ");
      }
   }

   public static IEnumerable<int> OddSequence(int start, int end)
   {
      if (start < 0 || start > 99)
         throw new ArgumentOutOfRangeException("start must be between 0 and 99.");
      if (end > 100)
         throw new ArgumentOutOfRangeException("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 following output:
//    Retrieved enumerator...
//    
//    Unhandled Exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
//    Parameter name: end must be less than or equal to 100.
//       at Sequence.<GetNumericRange>d__1.MoveNext() in Program.cs:line 23
//       at Example.Main() in Program.cs:line 43

Zamiast tego można zgłosić wyjątek podczas sprawdzania poprawności i przed pobraniem iteratora, zwracając iterator z funkcji lokalnej, jak pokazano w poniższym przykładzie.Instead, you can throw an exception when performing validation and before retrieving the iterator by returning the iterator from a local function, as the following example shows.

using System;
using System.Collections.Generic;

class Example
{
   static void Main()
   {
      IEnumerable<int> ienum = OddSequence(50, 110);
      Console.WriteLine("Retrieved enumerator...");
      
      foreach (var i in ienum)
      {
         Console.Write($"{i} ");
      }
   }

   public static IEnumerable<int> OddSequence(int start, int end)
   {
      if (start < 0 || start > 99)
         throw new ArgumentOutOfRangeException("start must be between 0 and 99.");
      if (end > 100)
         throw new ArgumentOutOfRangeException("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 following output:
//    Unhandled Exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
//    Parameter name: end must be less than or equal to 100.
//       at Sequence.<GetNumericRange>d__1.MoveNext() in Program.cs:line 23
//       at Example.Main() in Program.cs:line 43

Funkcji lokalnych można używać w podobny sposób, aby obsługiwać wyjątki poza operacją asynchroniczną.Local functions can be used in a similar way to handle exceptions outside of the asynchronous operation. Zwykle wyjątki zgłoszone w metodzie asynchronicznej wymagają sprawdzenia wewnętrznych wyjątków AggregateException.Ordinarily, exceptions thrown in async method require that you examine the inner exceptions of an AggregateException. Funkcje lokalne umożliwiają szybkie i niepowodzenie wykonywania kodu oraz umożliwiają synchroniczną i zaobserwowany wyjątek.Local functions allow your code to fail fast and allow your exception to be both thrown and observed synchronously.

Poniższy przykład używa metody asynchronicznej o nazwie GetMultipleAsync, aby wstrzymywać przez określoną liczbę sekund i zwracać wartość, która jest losowo wielokrotnością tej liczby sekund.The following example uses an asynchronous method named GetMultipleAsync to pause for a specified number of seconds and return a value that is a random multiple of that number of seconds. Maksymalne opóźnienie wynosi 5 sekund; wyniki ArgumentOutOfRangeException, jeśli wartość jest większa niż 5.The maximum delay is 5 seconds; an ArgumentOutOfRangeException results if the value is greater than 5. Jak pokazano na poniższym przykładzie, wyjątek, który jest generowany, gdy wartość 6 jest przekazana do metody GetMultipleAsync jest opakowany w AggregateException po rozpoczęciu wykonywania metody GetMultipleAsync.As the following example shows, the exception that is thrown when a value of 6 is passed to the GetMultipleAsync method is wrapped in an AggregateException after the GetMultipleAsync method begins execution.

using System;
using System.Threading.Tasks;

class Example
{
   static void Main()
   {
      int result = GetMultipleAsync(6).Result;
      Console.WriteLine($"The returned value is {result:N0}");
   }

   static async Task<int> GetMultipleAsync(int secondsDelay)
   {
      Console.WriteLine("Executing GetMultipleAsync...");
      if (secondsDelay < 0 || secondsDelay > 5)
         throw new ArgumentOutOfRangeException("secondsDelay cannot exceed 5.");
         
      await Task.Delay(secondsDelay * 1000);
      return secondsDelay * new Random().Next(2,10);
   } 
}
// The example displays the following output:
//    Executing GetMultipleAsync...
//
//    Unhandled Exception: System.AggregateException: 
//         One or more errors occurred. (Specified argument was out of the range of valid values.
//    Parameter name: secondsDelay cannot exceed 5.) ---> 
//         System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
//    Parameter name: secondsDelay cannot exceed 5.
//       at Example.<GetMultiple>d__1.MoveNext() in Program.cs:line 17
//       --- End of inner exception stack trace ---
//       at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
//       at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
//       at Example.Main() in C:\Users\ronpet\Documents\Visual Studio 2017\Projects\local-functions\async1\Program.cs:line 8

Podobnie jak w iteratorze metody, możemy resłużyć kod z tego przykładu, aby przeprowadzić walidację przed wywołaniem metody asynchronicznej.As we did with the method iterator, we can refactor the code from this example to perform the validation before calling the asynchronous method. Jak pokazano na poniższym przykładzie, ArgumentOutOfRangeException nie jest opakowany w AggregateException.As the output from the following example shows, the ArgumentOutOfRangeException is not wrapped in a AggregateException.

using System;
using System.Threading.Tasks;

class Example
{
   static void Main()
   {
      int result = GetMultiple(6).Result;
      Console.WriteLine($"The returned value is {result:N0}");
   }

   static Task<int> GetMultiple(int secondsDelay)
   {
      if (secondsDelay < 0 || secondsDelay > 5)
         throw new ArgumentOutOfRangeException("secondsDelay cannot exceed 5.");
         
      return GetValueAsync();
      
      async Task<int> GetValueAsync()
      {
         Console.WriteLine("Executing GetValueAsync...");
         await Task.Delay(secondsDelay * 1000);
         return secondsDelay * new Random().Next(2,10);
      }   
   } 
}
// The example displays the following output:
//    Unhandled Exception: System.ArgumentOutOfRangeException: 
//       Specified argument was out of the range of valid values.
//    Parameter name: secondsDelay cannot exceed 5.
//       at Example.GetMultiple(Int32 secondsDelay) in Program.cs:line 17
//       at Example.Main() in Program.cs:line 8

Zobacz takżeSee also