C#에서 랜덤 숫자 생성
[[academy-video-youtube({"vid": "GFLPncbEi-w", "start_time": "0", "title": "C#에서 랜덤 숫자 생성", "creator": "Tim Corey", "length": "10m 05s"})]]
C#에서 랜덤 숫자를 생성하는 것은 한 줄의 코드처럼 보입니다. 많은 경우 실제로 그렇습니다. 하지만 언어는 랜덤 값을 생성하는 여러 방법을 제공하며, 이들 간의 차이는 스레드 안전성, 재현성 및 사용 사례를 고려할 때 중요합니다. 잘못된 접근 방식을 선택하면 다중 스레드 코드에 미묘한 버그가 도입되거나 보고된 결함을 재현할 수 없게 만들 수 있습니다.
비디오 "C#에서 무작위 숫자 생성하기"에서 Tim Corey는 고전적인 Random 클래스를 통해 진행하며, 씨드 값이 존재하는 이유를 설명하고 현대의 기본값으로 Random.Shared을 도입합니다. 각 접근 방식을 다루면서 그 배경에 있는 이유를 설명하여 추측하지 않고 올바른 것을 선택할 수 있도록 도와드립니다. 왜 이렇게 간단해 보이는 것에 여러 경로가 있는지 궁금하셨다면, 이 기사가 이를 설명해줍니다.
데모 설정
[0:11 - 0:59] Tim은 Visual Studio 2026 (현재 미리보기)에서 실행 중인 콘솔 애플리케이션 내부에서 작업을 합니다. 보여진 모든 내용은 .NET 9 및 Visual Studio 2022에도 동일하게 적용되므로, 이미 설치된 도구와 함께 따라갈 수 있습니다.
데모 레이아웃은 각 반복마다 두 개의 무작위 값을 나란히 출력하는 for 루프입니다. 두 개의 출력이 병렬로 실행됨에 따라 두 생성기가 독립적으로 작동하는지 또는 동일한 결과를 산출하는지를 관찰하기가 쉬워집니다. 시드 값이 등장할 때 이 구분이 중요해집니다.
고전적 랜덤 클래스
[0:59 - 3:28] C#에서의 무작위 정수 생성의 원래 접근 방식은 Random 클래스의 인스턴스를 생성하는 것입니다:
Random rng1 = new Random();
Random rng2 = new Random();Random rng1 = new Random();
Random rng2 = new Random();각 인스턴스는 자체 내부 상태를 유지합니다. 어느 쪽이든 .Next(1, 101)를 호출하면 1에서 100 사이의 정수가 생성됩니다. Tim은 신입들이 자주 실수를 하는 세부 사항을 강조합니다: 최소값은 포함되지만 최대값은 제외됩니다. 1부터 100까지의 값을 원한다면 1와 101을 전달해야 하며, 1와 100은 아닙니다.
int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);애플리케이션을 실행하면 두 인스턴스가 서로 다른 시퀀스를 생성함을 확인합니다. 그 결과는 직관적으로 느껴지지만 두 인스턴스가 동일한 시작점을 공유할 때 무슨 일이 발생하는지에 대해서는 또 다른 이야기가 됩니다.
이 접근법에 관한 중요한 경고: 개별 Random 인스턴스는 스레드 안전하지 않습니다. 애플리케이션이 병렬 처리를 수행하고 여러 스레드가 동일한 인스턴스에 접근하면 내부 상태가 손상되어 0 또는 중복 값을 생성할 수 있습니다. 안전한 방법은 스레드마다 하나의 인스턴스를 생성하는 것입니다. 이 제약 사항이 언어가 나중에 더 나은 대안을 도입한 이유 중 하나입니다.
시드 값과 재생 가능한 시퀀스
[3:28 - 6:00] Tim은 나중에 두 생성자에 명시적인 시드를 전달합니다:
Random rng1 = new Random(25);
Random rng2 = new Random(25);Random rng1 = new Random(25);
Random rng2 = new Random(25);출력이 크게 달라집니다. 이제 두 생성기가 동일한 시퀀스를 생성합니다: 79, 16, 25, 90, 50, 41, 등등. 시드를 모르면 개별 숫자는 여전히 예측할 수 없지만 같은 시작 값을 주면 진행은 결정적입니다.
왜 그렇게 원할까요? Tim은 실용적인 예를 제공합니다. 세션 중 랜덤 이벤트를 생성하는 게임을 상상해 보세요. 플레이어가 버그를 보고하지만, 결과가 랜덤화되어 재현할 수 없습니다. 그 세션에서 사용된 씨드를 기록하면, 개발자는 동일한 값으로 새 Random 인스턴스를 초기화하여 정확한 의사 결정 연쇄를 재현할 수 있습니다. 이 동일한 논리는 랜덤한 동작에 대해 신뢰할 수 있는 단언을 작성해야 하는 단위 테스트 시나리오에도 적용됩니다.
시드된 인스턴스는 제어된 랜덤화를 제공합니다: 예측할 수 없게 보이지만 요청 시 재생할 수 있는 시퀀스입니다. 그 기능이 고전적인 Random 생성자가 씨드를 받아들이는 이유입니다. 이는 단순한 API가 존재함에도 불구하고 더 이상 사용되지 않은 이유입니다.
Random.Shared: 현대적 기본값
[7:36 - 9:01] .NET 6부터 대부분의 무작위 숫자 생성에 대한 권장 접근은 Random.Shared입니다:
int output1 = Random.Shared.Next(1, 101);
int output2 = Random.Shared.Next(1, 101);int output1 = Random.Shared.Next(1, 101);
int output2 = Random.Shared.Next(1, 101);인스턴스화가 필요 없습니다. Random.Shared은 런타임에서 관리되는 정적이고 스레드 안전한 인스턴스입니다. .Next() (또는 Random 클래스의 다른 메서드)을 호출하면 객체의 수명이나 동시성에 대해 걱정하지 않고 값을 받을 수 있습니다.
Tim은 이를 증명하기 위해 두 번의 데모를 실행합니다. 첫 번째 실행은 94와 91로 시작합니다; 두 번째는 42와 70에서 시작합니다. 씨드 인스턴스와 달리 Random.Shared는 프로세스가 시작될 때마다 다른 시작 상태에서 값을 가져옵니다. 시드를 설정할 수 없으므로 이 API를 통해 재현 가능한 시퀀스를 생성할 수 없습니다. 그것이 트레이드 오프입니다: 단순성과 안전성을 제공하는 대신 결정론적 재생을 포기하게 됩니다.
.Next()를 넘어 Random.Shared는 더블 생성, 바이트 배열 채우기, 콜렉션 셔플링을 위한 메서드를 공개합니다. 의식 없이 빠르게 랜덤 값을 필요로 하는 대부분의 애플리케이션 코드에서는 이 단일 정적 속성이 자체 인스턴스를 관리하는 보일러플레이트를 대체합니다.
올바른 접근법 선택하기
[9:01 - 9:30] Tim은 간결한 의사 결정 프레임워크로 마무리합니다. 일상적인 무작위성 (값 선택, 리스트 셔플링, 무작위 요소 선택)에는 Random.Shared이 적합합니다. 설정이 필요 없고, 동시성을 처리하며 스레드 전반에서 올바르게 동작합니다.
디버깅, 테스트, 또는 시뮬레이션 재생을 위한 반복 가능한 출력 시리즈가 필요할 때는 알려진 씨드로 전용 Random 인스턴스를 생성하세요. 그 인스턴스는 스레드 간에 공유하면 안전하지 않다는 점을 염두에 두세요.
그리고 보안 관련 토큰, 키, 비밀번호 소금 등을 포함하는 모든 것에 대해서는 이 접근법 중 어느 것도 적절하지 않습니다. Tim은 예측에 저항력이 있으며 무작위적인 값을 생성하는 System.Security.Cryptography의 암호화 라이브러리를 시청자에게 안내합니다.
총정리: 간단한 API, 의미 있는 차이점
[9:30 - 9:50] 이 주제를 기만적으로 어렵게 만드는 것은 얼마나 적은 코드가 필요한지입니다. 한 줄이면 어느 접근 방식으로든 랜덤 숫자를 생성할 수 있습니다. 복잡성은 문법에 있지 않고 각 메서드가 제공하는 보증이 무엇인지, 즉 스레드 안전성, 재현성 또는 암호 강도에 있습니다.
결론
[9:50 - 10:05] 요약하자면: Random.Shared는 제로 설정과 내장 스레드 안전성을 가진 대부분의 필요를 충족합니다. 씨드가 있는 Random 인스턴스를 사용하면 디버깅 또는 테스트 시 특정 시퀀스를 재현할 수 있습니다. 암호 생성기는 예측 가능성이 취약성인 보안-민감 코드에 속합니다
다음 번 C#에서 랜덤 번호를 찾을 때, 결정은 한 가지 질문으로 좁혀집니다: 나중에 이 시퀀스를 재생할 필요가 있습니까? 만약 답이 '아니요'라면, 여러분이 필요한 것은 Random.Shared입니다.
예제 팁: Random.Shared.Next(min, max)를 호출할 때 max가 제외된다는 점을 주목하세요. 1에서 100 사이 범위는 1와 101를 전달해야 합니다. 이 잘못된 1차 경계는 시드된 인스턴스에도 적용됩니다.

