로컬 함수(C# 프로그래밍 가이드)Local functions (C# Programming Guide)

C# 7.0부터 C#에서는 로컬 함수를 지원합니다.Starting with C# 7.0, C# supports local functions. 로컬 함수는 다른 멤버에 중첩된 형식의 private 메서드입니다.Local functions are private methods of a type that are nested in another member. 포함하는 멤버에서만 호출할 수 있습니다.They can only be called from their containing member. 로컬 함수는 다음에서 선언하고 호출할 수 있습니다.Local functions can be declared in and called from:

  • 메서드, 특히 반복기 메서드 및 비동기 메서드Methods, especially iterator methods and async methods
  • 생성자Constructors
  • 속성 접근자Property accessors
  • 이벤트 접근자Event accessors
  • 무명 메서드Anonymous methods
  • 람다 식Lambda expressions
  • 종료자Finalizers
  • 다른 로컬 함수Other local functions

그러나 식 본문 멤버 내에서는 로컬 함수를 선언할 수 없습니다.However, local functions can't be declared inside an expression-bodied member.

참고

로컬 함수에서도 지원하는 기능을 람다 식으로 구현할 수 있는 경우도 있습니다.In some cases, you can use a lambda expression to implement functionality also supported by a local function. 비교를 보려면 로컬 함수 및 람다 식 비교를 참조하세요.For a comparison, see Local functions compared to Lambda expressions.

로컬 함수는 코드의 의도를 명확하게 합니다.Local functions make the intent of your code clear. 코드를 읽는 모든 사용자가 포함하는 메서드를 통해서만 메서드를 호출할 수 있음을 알 수 있습니다.Anyone reading your code can see that the method is not callable except by the containing method. 팀 프로젝트의 경우 로컬 함수를 사용하면 다른 개발자가 실수로 클래스 또는 구조체의 다른 곳에서 직접 메서드를 호출하는 경우를 방지할 수도 있습니다.For team projects, they also make it impossible for another developer to mistakenly call the method directly from elsewhere in the class or struct.

로컬 함수 구문Local function syntax

로컬 함수는 포함하는 멤버 내의 중첩 메서드로 정의됩니다.A local function is defined as a nested method inside a containing member. 해당 정의는 다음 구문을 사용합니다.Its definition has the following syntax:

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

로컬 함수는 asyncunsafe 한정자를 사용할 수 있습니다.Local functions can use the async and unsafe modifiers.

해당 메서드 매개 변수를 비롯하여 포함하는 멤버에 정의된 모든 지역 변수는 로컬 함수에서 액세스할 수 있습니다.Note that all local variables that are defined in the containing member, including its method parameters, are accessible in the local function.

메서드 정의와 달리 로컬 함수 정의에는 다음 요소를 포함할 수 없습니다.Unlike a method definition, a local function definition cannot include the following elements:

  • 멤버 액세스 한정자.The member access modifier. 모든 로컬 함수는 private이므로 private 키워드 등의 액세스 한정자를 포함하면 컴파일러 오류 CS0106,“이 항목의 ‘private’ 한정자가 유효하지 않습니다.”가 생성됩니다.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."

  • static 키워드입니다.The static keyword. static 키워드를 포함하면 컴파일러 오류 CS0106, “이 항목의 ‘static’ 한정자가 유효하지 않습니다.”가 생성됩니다.Including the static keyword generates compiler error CS0106, "The modifier 'static' is not valid for this item."

또한 로컬 함수나 해당 매개 변수 및 형식 매개 변수에는 특성을 적용할 수 없습니다.In addition, attributes can't be applied to the local function or to its parameters and type parameters.

다음 예제에서는 GetText 메서드에 대해 private인 로컬 함수 AppendPathSeparator를 정의합니다.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;   
         }
    } 
}

로컬 함수 및 예외Local functions and exceptions

로컬 함수의 유용한 기능 중 하나는 예외가 즉시 나타나도록 할 수 있다는 것입니다.One of the useful features of local functions is that they can allow exceptions to surface immediately. 메서드 반복기의 경우 반환된 시퀀스가 열거된 경우에만 예외가 나타나고 반복기를 검색할 때는 나타나지 않습니다.For method iterators, exceptions are surfaced only when the returned sequence is enumerated, and not when the iterator is retrieved. 비동기 메서드의 경우 비동기 메서드에서 throw된 예외는 반환된 작업을 대기할 때 관찰됩니다.For async methods, any exceptions thrown in an async method are observed when the returned task is awaited.

다음 예제에서는 지정된 범위 사이의 홀수를 열거하는 OddSequence 메서드를 정의합니다.The following example defines an OddSequence method that enumerates odd numbers between a specified range. 100보다 큰 숫자를 OddSequence 열거자 메서드에 전달하기 때문에 메서드가 ArgumentOutOfRangeException을 throw합니다.Because it passes a number greater than 100 to the OddSequence enumerator method, the method throws an ArgumentOutOfRangeException. 예제의 출력에서 볼 수 있듯이 예외는 숫자를 반복하는 경우에만 나타나고 열거자를 검색할 때는 나타나지 않습니다.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

대신, 다음 예제와 같이 로컬 함수에서 반복기를 반환하여 유효성 검사를 수행할 때, 반복기를 검색하기 전에 예외를 throw할 수 있습니다.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

유사한 방식으로 로컬 함수를 사용하여 비동기 작업 외부에서 예외를 처리할 수 있습니다.Local functions can be used in a similar way to handle exceptions outside of the asynchronous operation. 일반적으로 비동기 메서드에서 throw된 예외의 경우 AggregateException의 내부 예외를 검사해야 합니다.Ordinarily, exceptions thrown in async method require that you examine the inner exceptions of an AggregateException. 로컬 함수를 사용하면 코드가 빨리 실패하여 예외가 throw되는 동시에 관찰할 수 있습니다.Local functions allow your code to fail fast and allow your exception to be both thrown and observed synchronously.

다음 예제에서는 GetMultipleAsync라는 비동기 메서드를 사용하여 지정된 시간(초) 동안 일시 중지하고 해당 시간(초)의 임의 배수인 값을 반환합니다.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. 최대 지연 시간은 5초입니다. 값이 5보다 크면 ArgumentOutOfRangeException이 발생합니다.The maximum delay is 5 seconds; an ArgumentOutOfRangeException results if the value is greater than 5. 다음 예제와 같이 값 6이 GetMultipleAsync 메서드에 전달될 때 throw되는 예외는 GetMultipleAsync 메서드 실행이 시작된 후에 AggregateException에 래핑됩니다.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

메서드 반복기와 마찬가지로, 이 예제의 코드를 리팩터링하여 비동기 메서드를 호출하기 전에 유효성 검사를 수행할 수 있습니다.As we did with the method iterator, we can refactor the code from this example to perform the validation before calling the asynchronous method. 다음 예제의 출력에서 볼 수 있듯이, ArgumentOutOfRangeExceptionAggregateException에 래핑되지 않습니다.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


참고 항목See also