* Random 공식 레포지토리
runtime/src/libraries/System.Private.CoreLib/src/System/Random.cs at main · dotnet/runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps. - dotnet/runtime
github.com
* ImplBase 공식 레포지토리
runtime/src/libraries/System.Private.CoreLib/src/System/Random.ImplBase.cs at main · dotnet/runtime
.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps. - dotnet/runtime
github.com
* seed
난수를 만드는 알고리즘의 초기값
즉, 난수열을 시작할 때 첫 번째로 들어가는 값이다
[난수열]
난수들이 일정한 규칙(또는 물리적 무작위성)에 따라 순서대로 나열된 숫자들의 연속(Sequence)
즉, 하나의 seed로 시작해 연속된 무작위 숫자 시퀀스를 만들어 낸다
[왜 중요한가]
난수를 만드는 알고리즘은 수학 공식으로 동작하기 때문에
동일한 Seed → 동일한 난수 시퀀스를 만든다
* 유사난수(Pseudo-Random Number, 의사난수)
컴퓨터가 일정한 규칙을 사용해 마치 임의의 렌덤 값처럼 생성한 수
즉, 골고루 나오도록 만든 임의의 수
(예) 게임에서 가챠
* .NET의 System.Random 클래스 - 2가지 방법이 있다
| 종류 | 생성자 사용 | 난수 생성 로직 | 의미 |
| 기본 생성자 | Random() |
XoshiroImpl() | 최신 고성능 PRNG (Seed 없이 내부에서 자동 초기화) |
| Seed 생성자 | Random(int seed) | CompatSeedImpl(seed) | 과거 .NET과 호환되는 Seed 기반 LCG PRNG |
[주의]
thread-safe가 아니므로 다중 스레드 환경에서는 충돌 가능성이 있다
[만약]
안전한 Random을 만들고 싶다면 Shared 프라퍼티를 쓰면 된다
즉, Shared 프로퍼티는 내부적으로 private protected Random(true)를 호출해 ThreadSafeRandom을 생성하는 것과 같다

/// <summary>
/// ThreadSafeRandom에서 사용하는 생성자이다.
/// </summary>
/// <param name="isThreadSafeRandom">
/// 반드시 true여야 한다.
/// </param>
private protected Random(bool isThreadSafeRandom)
{
Debug.Assert(isThreadSafeRandom);
_impl = null!; // 기본 구현체는 전혀 사용되지 않는다.
}
/// <summary>
/// 모든 스레드에서 동시에 사용할 수 있는 thread-safe Random 인스턴스를 제공한다.
/// </summary>
public static Random Shared { get; } = new ThreadSafeRandom();
* Random 생성 방법 총정리
// 1. 기본 생성자
Random random1 = new Random();
// 2. Seed 생성자
Random random2 = new Random(12345);
// 3. Thread Safe - 여러 스레드에서 동시에 안전하게 사용 가능
// 3-1. 전역 Shared
// - 업캐스팅해서 Random 타입으로 사용, 내부적으로 ThreadSafeRandom
// - seed 지정 불가능
Random random3_1 = Random.Shared;
// 3-2. 스레드별 Random (스레드마다 독립 인스턴스, ThreadLocal)
// - 여러 스레드에서 충돌 없이 각각의 Random 사용
var localRandom3_2_1 = new ThreadLocal<Random>(() => new Random());
var localRandom3_2_2 = new ThreadLocal<Random>(() => new Random(12345));
* ImplBase로 난수 알고리즘을 선택하는 .NET Random의 내부 구조
생성자에 따라 내부적으로 ImplBase 구현체를 연결한다
(1) readonly로 선언된 ImplBase는 생성 시점에 난수 알고리즘을 한 번만 선택한다는 의미이다

/// <summary>
/// 실제 난수 생성 로직을 담당하는 내부 구현체.
/// </summary>
/// <remarks>
/// Random 인스턴스의 생성 방식에 따라 서로 다른 생성기를 선택할 수 있도록 분리되어 있다.
/// seed를 사용해 생성된 경우, 동일한 시퀀스를 기대하는 사용자를 위해 이전 버전과의 호환성을 보장해야 한다.
/// Random을 상속한 파생 타입이라면, 파생 클래스의 오버라이드 메서드가 항상 정상 호출되도록 해야 한다.
/// 반면 기본 Random 타입이고 기본 생성자를 사용하면, 성능과 품질을 최대화할 수 있도록 내부적으로 가장 적합한 생성기를 선택한다.
/// </remarks>
private readonly ImplBase _impl;
(2) 생성자 선택에 따라 _impl 필드에 설정되는 구현체가 달라진다

/// <summary>
/// 기본 시드 값을 사용하여 새로운 Random 클래스 인스턴스를 초기화한다.
/// </summary>
public Random() =>
// 시드가 지정되지 않은 경우:
// - 만약 현재 타입이 기본 Random 타입이면 원하는 방식(Xoshiro)으로 구현 가능
// - 파생 타입이면 기존 구현 방식(호환)을 유지하여 이전처럼 오버라이드가 호출되도록 함
_impl = GetType() == typeof(Random) ? new XoshiroImpl() : new CompatDerivedImpl(this);
/// <summary>
/// 지정된 시드 값을 사용하여 새로운 Random 클래스 인스턴스를 초기화한다.
/// </summary>
/// <param name="Seed">
/// 의사 난수 시퀀스의 시작 값을 계산하는 데 사용되는 숫자.
/// 음수가 지정되면 해당 숫자의 절대값이 사용된다.
/// </param>
public Random(int Seed) =>
// 사용자 지정 시드가 있는 경우:
// - 기본 Random 타입이면 과거 방식(LCG 기반)을 유지하여 동일한 결과를 보장
// - 파생 타입이면 이전처럼 동일한 오버라이드 로직 사용
_impl = GetType() == typeof(Random)
? new CompatSeedImpl(Seed)
: new CompatDerivedImpl(this, Seed);
'IT공부' 카테고리의 다른 글
| [Unity] 수학과 Unity 관점에서 벡터와 스칼라 정리 (0) | 2025.06.28 |
|---|---|
| [C#] .NET 가비지 컬렉션(GC, Garbage collection) 둘러보기 (0) | 2025.06.25 |
| [C#] .NET 9 decimal 구조체 톺아보기 (0) | 2025.06.16 |
| [C#, Unity] 비동기 함수와 코루틴의 차이 (0) | 2025.05.05 |
| [Unity] 왜 .cs 파일을 스크립트 파일이라고 부를까? 그리고 인터프리터 언어인가? (0) | 2025.04.27 |