IT공부

[C#] .NET 9 Random 클래스 살펴보기

shine94 2025. 6. 18. 15:18

Random 공식 레포지토리

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Random.cs

 

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 공식 레포지토리

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Random.ImplBase.cs#L12

 

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);