이 자습서에서는 .NET 및 C# 언어의 다양한 기능을 설명합니다. 다음 내용을 배웁니다.
- .NET CLI의 기본 사항
- C# 콘솔 애플리케이션의 구조
- 콘솔 I/O
- .NET의 파일 I/O API 기본 사항
- .NET에서 작업 기반 비동기 프로그래밍의 기본 사항
텍스트 파일을 읽고 해당 텍스트 파일의 내용을 콘솔에 에코하는 애플리케이션을 빌드합니다. 콘솔의 출력 속도는 소리 내어 읽는 속도와 일치하도록 조정됩니다. '<(작음)' 또는 '>(큼)' 키를 눌러 속도를 빠르게 또는 느리게 조절할 수 있습니다. Windows, Linux, macOS 또는 Docker 컨테이너에서 이 애플리케이션을 실행할 수 있습니다.
이 자습서에는 많은 기능이 있습니다. 하나씩 빌드해 보겠습니다.
필수 조건
- 최신 .NET SDK
- Visual Studio Code 편집기
- C# 개발 키트
앱 만들기
첫 번째 단계는 새 애플리케이션을 만드는 것입니다. 명령 프롬프트를 열고 애플리케이션에 대한 새 디렉터리를 만듭니다. 현재 디렉터리로 설정하십시오. 명령 프롬프트에 명령을 dotnet new console 입력합니다. 다음은 그 예입니다.
E:\development\VSprojects>mkdir teleprompter
E:\development\VSprojects>cd teleprompter
E:\development\VSprojects\teleprompter>dotnet new console
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on E:\development\VSprojects\teleprompter\teleprompter.csproj...
Determining projects to restore...
Restored E:\development\VSprojects\teleprompter\teleprompter.csproj (in 78 ms).
Restore succeeded.
그러면 기본 "Hello World" 애플리케이션에 대한 시작 파일이 만들어집니다.
수정을 시작하기 전에 간단한 Hello World 애플리케이션을 실행해 보겠습니다. 애플리케이션을 만든 후 명령 프롬프트에 입력 dotnet run 합니다. 이 명령은 NuGet 패키지 복원 프로세스를 실행하고, 애플리케이션 실행 파일을 만들고, 실행 파일을 실행합니다.
간단한 Hello World 애플리케이션 코드는 모두 Program.cs. 즐겨 찾는 텍스트 편집기를 사용하여 해당 파일을 엽니다. Program.cs 코드를 다음 코드로 바꿉다.
namespace TeleprompterConsole;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
파일 맨 위의 namespace 문을 확인하세요. 사용했을 수 있는 다른 개체 지향 언어와 마찬가지로 C#은 네임스페이스를 사용하여 형식을 구성합니다. 이 Hello World 프로그램도 다르지 않습니다. 프로그램이 네임스페이스에 이름이 TeleprompterConsole있는 것을 볼 수 있습니다.
파일 읽기 및 출력
추가할 첫 번째 기능은 텍스트 파일을 읽고 해당 모든 텍스트를 콘솔에 표시하는 기능입니다. 먼저 텍스트 파일을 추가해 보겠습니다. 이 샘플에 대한 GitHub 리포지토리의 sampleQuotes.txt 파일을 프로젝트 디렉터리에 복사합니다. 애플리케이션에 대한 스크립트 역할을 합니다. 이 자습서의 샘플 앱을 다운로드하는 방법에 대한 자세한 내용은 샘플 및 자습서의 지침을 참조하세요.
다음으로 클래스에 다음 메서드를 추가합니다 Program (메서드 바로 아래 Main ).
static IEnumerable<string> ReadFrom(string file)
{
string? line;
using (var reader = File.OpenText(file))
{
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
이 메서드는 반복기 메서드라는 특별한 형식의 C# 메서드입니다. 반복기 메서드는 시퀀스를 지연 평가하여 반환합니다. 즉, 시퀀스의 각 항목은 시퀀스를 사용하는 코드에 의해 요청될 때 생성됩니다. 반복기 메서드는 하나 이상의 yield return 문을 포함하는 메서드입니다. 메서드에서 반환된 ReadFrom 개체에는 시퀀스의 각 항목을 생성하는 코드가 포함되어 있습니다. 이 예제에서는 원본 파일에서 다음 텍스트 줄을 읽고 해당 문자열을 반환하는 작업이 포함됩니다. 호출 코드가 시퀀스에서 다음 항목을 요청할 때마다 코드는 파일에서 다음 텍스트 줄을 읽고 반환합니다. 파일이 완전히 읽히면 시퀀스는 더 이상 항목이 없음을 나타냅니다.
새로운 두 가지 C# 구문 요소가 있습니다. 이 메서드의 using 문장은 리소스 정리를 관리합니다. 문에서 using 초기화된 변수(reader이 예제에서는)는 인터페이스를 IDisposable 구현해야 합니다. 해당 인터페이스는 리소스를 해제할 때 호출해야 하는 단일 메서드 Dispose를 정의합니다. 컴파일러는 실행이 using 문의 닫는 중괄호에 도달할 때 해당 호출을 생성합니다. 컴파일러에서 생성된 코드는 using 문으로 정의된 블록의 코드에서 예외가 throw되더라도 리소스가 해제되도록 합니다.
변수는 reader 키워드를 사용하여 정의됩니다 var .
var 는 암시적으로 형식화된 지역 변수를 정의합니다. 즉, 변수의 형식은 변수에 할당된 개체의 컴파일 시간 형식에 따라 결정됩니다. 여기서는 OpenText(String) 메서드의 반환 값이며, 이는 StreamReader 개체입니다.
이제 Main 메서드에서 파일을 읽기 위해 코드를 작성해 봅시다.
var lines = ReadFrom("sampleQuotes.txt");
foreach (var line in lines)
{
Console.WriteLine(line);
}
프로그램을 실행(사용 dotnet run)하면 콘솔에 인쇄된 모든 줄을 볼 수 있습니다.
지연 추가 및 출력 서식 지정
당신이 가지고있는 것은 소리 내어 읽기에 너무 빨리 표시됩니다. 이제 출력에 지연을 추가해야 합니다. 시작하면 비동기 처리를 가능하게 하는 일부 핵심 코드를 빌드하게 됩니다. 그러나 이러한 첫 번째 단계는 몇 가지 안티패턴을 따릅니다. 코드를 추가할 때 주석에서 안티패턴이 지적되고 코드는 이후 단계에서 업데이트됩니다.
이 섹션에는 두 단계가 있습니다. 먼저 전체 줄 대신 단일 단어를 반환하도록 반복기 메서드를 업데이트합니다. 이러한 수정을 통해 작업이 완료됩니다.
yield return line; 문을 다음 코드로 바꿉니다.
var words = line.Split(' ');
foreach (var word in words)
{
yield return word + " ";
}
yield return Environment.NewLine;
다음으로 파일의 각 줄을 처리하는 방법을 수정하고, 각 단어를 기록한 후 지연을 추가해야 합니다. 메서드의 Console.WriteLine(line) 문을 Main 다음 블록으로 바꿉니다.
Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
{
var pause = Task.Delay(200);
// Synchronously waiting on a task is an
// anti-pattern. This will get fixed in later
// steps.
pause.Wait();
}
샘플을 실행하고 출력을 확인합니다. 이제 각 단어가 인쇄되고 200ms 지연이 표시됩니다. 그러나 원본 텍스트 파일에 줄 바꿈이 없는 80자 이상의 여러 줄이 있으므로 표시된 출력에 몇 가지 문제가 표시됩니다. 스크롤하는 동안 읽기 어려울 수 있습니다. 이는 쉽게 해결할 수 있습니다. 각 줄의 길이를 추적하고 줄 길이가 특정 임계값에 도달할 때마다 새 줄을 생성합니다. 줄 길이를 포함하는 메서드의 wordsReadFrom 선언 후 지역 변수를 선언합니다.
var lineLength = 0;
그런 다음 문 다음에 다음 코드를 추가합니다(닫는 yield return word + " "; 중괄호 앞).
lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}
샘플을 실행하면 미리 구성된 속도로 소리내어 읽을 수 있습니다.
비동기 작업
이 마지막 단계에서는 한 작업에서 출력을 비동기적으로 작성하는 코드를 추가하고, 다른 작업을 실행하여 텍스트 표시 속도를 향상 또는 늦추거나 텍스트 표시를 완전히 중지하려는 경우 사용자의 입력을 읽습니다. 여기에는 몇 가지 단계가 있으며, 결국에는 필요한 모든 업데이트가 제공됩니다. 첫 번째 단계는 파일을 읽고 표시하기 위해 지금까지 만든 코드를 나타내는 비동 Task 기 반환 메서드를 만드는 것입니다.
이 메서드를 Program 클래스에 추가합니다 (이는 Main 메서드 본문에서 가져온 것입니다).
private static async Task ShowTeleprompter()
{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(200);
}
}
}
두 가지 변경 내용을 확인할 수 있습니다. 먼저 메서드 본문에서 작업이 완료되기를 동기적으로 대기하도록 호출 Wait() 하는 대신 이 버전에서는 키워드를 await 사용합니다. 이렇게 하려면 메서드 서명에 async 한정자를 추가해야 합니다. 이 메서드는 Task를 반환합니다.
Task 개체를 반환하는 반환 문이 없음을 주목하십시오. 대신 해당 Task 객체는 await 연산자를 사용할 때 컴파일러가 생성하는 코드에 의해 생성됩니다. 이 메서드가 await에 도달하면 반환될 것을 상상할 수 있습니다. 반환된 Task 값은 작업이 완료되지 않았음을 나타냅니다. 대기 중인 작업이 완료되면 메서드가 다시 시작됩니다. 완료까지 실행되면 반환 Task 된 항목은 완료되었음을 나타냅니다.
호출 코드는 반환된 Task 코드를 모니터링하여 완료 시기를 확인할 수 있습니다.
ShowTeleprompter 호출 전에 await 키워드를 추가합니다.
await ShowTeleprompter();
이렇게 하려면 메서드 서명을 다음으로 변경 Main 해야 합니다.
static async Task Main(string[] args)
기본 사항 섹션의 async Main 메서드 에 대해 자세히 알아봅니다.
다음으로 콘솔에서 읽을 두 번째 비동기 메서드를 작성하고 '<'(보다 작음), '>'(보다 큼) 및 'X' 또는 'x' 키를 확인해야 합니다. 해당 작업에 대해 추가하는 방법은 다음과 같습니다.
private static async Task GetInput()
{
var delay = 200;
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
{
delay -= 10;
}
else if (key.KeyChar == '<')
{
delay += 10;
}
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
{
break;
}
} while (true);
};
await Task.Run(work);
}
이렇게 하면 콘솔에서 키를 읽는 대리자를 나타내는 Action 람다 식이 만들어지고 사용자가 ''(보다 작음) 또는 '<>'(보다 큼) 키를 누를 때 지연을 나타내는 지역 변수를 수정합니다. 대리자 메서드는 사용자가 언제든지 텍스트 표시를 중지할 수 있도록 'X' 또는 'x' 키를 누르면 완료됩니다. 이 메서드는 사용자가 키를 누를 때까지 차단하고 기다리는 데 사용합니다 ReadKey() .
이 두 작업 간의 공유 데이터를 처리할 수 있는 클래스를 만들어야 합니다. 이 클래스에는 지연과 파일이 완전히 읽혀졌음을 나타내는 플래그 Done 라는 두 개의 공용 속성이 포함됩니다.
using static System.Math;
namespace TeleprompterConsole;
internal class TelePrompterConfig
{
public int DelayInMilliseconds { get; private set; } = 200;
public void UpdateDelay(int increment) // negative to speed up
{
var newDelay = Min(DelayInMilliseconds + increment, 1000);
newDelay = Max(newDelay, 20);
DelayInMilliseconds = newDelay;
}
public bool Done { get; private set; }
public void SetDone()
{
Done = true;
}
}
새 파일을 만듭니다. .cs 끝나는 모든 이름일 수 있습니다. 예를 들어 TelePrompterConfig.cs. TelePrompterConfig 클래스 코드를 붙여넣고 저장하고 닫습니다. 표시된 대로 네임스페이 TeleprompterConsole 스에 해당 클래스를 배치합니다. 주의하세요, using static 문을 사용하면 외부 클래스나 네임스페이스 이름 없이 Min 및 Max 메서드를 참조할 수 있습니다. 한 문은 using static 한 클래스에서 메서드를 불러옵니다. 이는 static 없이 모든 클래스를 네임스페이스에서 가져오는 using문과 대조적입니다.
다음으로, ShowTeleprompter 및 GetInput 메서드를 업데이트하여 새 config 개체를 사용해야 합니다. 이 기능을 완료하려면 이러한 작업(및ShowTeleprompter)을 모두 시작하고 이러한 두 작업GetInput 간의 공유 데이터도 관리하는 새 async Task 반환 메서드를 만들어야 합니다. RunTelePrompter 작업을 만들어 두 작업을 모두 시작하고 첫 번째 작업이 완료되면 종료합니다.
private static async Task RunTeleprompter()
{
var config = new TelePrompterConfig();
var displayTask = ShowTeleprompter(config);
var speedTask = GetInput(config);
await Task.WhenAny(displayTask, speedTask);
}
여기서 새로운 방법 중 하나는 호출입니다 WhenAny(Task[]) . 인수 목록에 있는 작업 중 하나가 완료되면 즉시 완료되는 Task 작업이 생성됩니다.
다음으로, ShowTeleprompter 메서드와 GetInput 메서드를 모두 config 객체를 사용하여 지연을 처리하도록 업데이트해야 합니다. 구성 개체가 이러한 메서드에 매개 변수로 전달되고 있습니다. 복사/붙여넣기를 사용하여 메서드를 여기서 새 코드로 완전히 바꿉다. 코드가 구성 개체에서 특성을 사용하고 메서드를 호출하는 것을 볼 수 있습니다.
private static async Task ShowTeleprompter(TelePrompterConfig config)
{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(config.DelayInMilliseconds);
}
}
config.SetDone();
}
private static async Task GetInput(TelePrompterConfig config)
{
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
config.UpdateDelay(-10);
else if (key.KeyChar == '<')
config.UpdateDelay(10);
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
config.SetDone();
} while (!config.Done);
};
await Task.Run(work);
}
이제 Main을(를) ShowTeleprompter 대신 RunTeleprompter을(를) 호출하도록 업데이트해야 합니다.
await RunTeleprompter();
결론
이 자습서에서는 콘솔 애플리케이션에서의 작업과 관련된 C# 언어 및 .NET Core 라이브러리와 관련된 다양한 기능을 보여 줍니다. 이 지식을 바탕으로 언어 및 여기에 소개된 클래스에 대해 자세히 살펴볼 수 있습니다. 파일 및 콘솔 I/O의 기본 사항, 작업 기반 비동기 프로그래밍의 차단 및 비차단 사용, C# 언어 둘러보기 및 C# 프로그램 구성 방법 및 .NET CLI를 살펴보았습니다.
파일 I/O에 대한 자세한 내용은 파일 및 스트림 I/O를 참조하세요. 이 자습서에서 사용되는 비동기 프로그래밍 모델에 대한 자세한 내용은 작업 기반 비동기 프로그래밍 및 비동기 프로그래밍을 참조하세요.
.NET