Determinism, Localization & Gotchas
Deterministic generation
Every Forge wraps an RNG. Pass your own Random instance and the same seed always reproduces the same sequence:
var forge = new Forge(new Random(42), CultureInfo.InvariantCulture);
var faker = new PersonFaker { /* ... same configuration ... */ };
var first = faker.Get();
var second = faker.Get();
// first == second, every runTwo Forge instances built with the same seed and locale will produce identical data — the basis for golden-file style tests. Without a seed (default Forge), Random.Shared and InvariantCulture are used and every run differs.
TIP
Forge with a new Random() (no seed) per run when you want varied fixtures, e.g. in [SetUp]/[TestInitialize], and a seeded Forge inside tests that assert structure.
Localization
Pass a CultureInfo to a Forge and generators pull their word lists from locale data files.
var frenchForge = new Forge(new Random(7), new CultureInfo("fr-FR"));
var faker = new PersonFaker
{
FirstName = f => f.Person.FirstName(),
/* ... */
};Data availability
Locale data ships as embedded JSON resources under Forged.Core/Locales/.... Only the en locale is shipped with the package as of v1.2.0, but the lookup logic supports any culture:
- Lookup order is exact locale → base language →
en.fr-FR⇒ triesfr-FR, thenfr, thenen.fr⇒ triesfr, thenen.en-GB⇒ triesen-GB, thenen.
- If no file matches any of those, the generator falls back to a reasonable default (the file loader's embedded resources), so generation never fails for an unsupported locale.
- Files are parsed once and cached in a static
ConcurrentDictionarykeyed by(locale, file)and shared across allForgeinstances for the process lifetime. RepeatedGet()calls reuse the parsed data — cheap to call, but note the cache is process-global, so hot-reload/edited.jsondata in tests won't be re-read without a fresh process.
If you add your own locale data, place files at the expected resource path for your culture (e.g. Locales/fr/person/name/last.jsonc) and build them into the library — or contribute them upstream. Structure mirrors the en files (JSON, comments allowed).
Gotchas
Faker.Get(min, max) max is exclusive
Get(3, 6) yields between 3 and 5 items — the upper bound behaves like Random.Next(min, max).
faker.Get(3, 6).Count(); // 3, 4, or 5
faker.Get(3, 6).Count() <= 5; // always trueout var is not allowed in object initializers
To use Memo, declare the memo variables first:
MemoValueGenerator<string> first = null!;
MemoValueGenerator<string> last = null!;
var faker = new PersonFaker
{
FirstName = f => f.Text.Pronounceable(1, 3).Capitalize().Memo(out first),
LastName = f => f.Text.Pronounceable(1, 3).ToLower().Capitalize().Memo(out last),
FullName = f => f.Basic.Func(() => $"{first} {last}"),
};Only public settable properties are faked
The source generator looks at properties with a public set/init. Get-only properties and public fields are ignored — they keep whatever initial value the class sets. This is by design: Forged never writes through reflection, so fakes can't mutate readonly state.
required properties behave differently: the generator emits a compile-time assignment for them, so you must configure a generator for every required member or your code won't compile.
Nullable value type members need .Nullable() / .OrNull()
A DateTime? property assigned a Generator<DateTime> is a type mismatch. Convert explicitly:
DateOfBirth = f => f.Temporal.Past().Nullable(), // DateTime -> DateTime?
MaybeLogout = f => f.Temporal.Past().OrNull(.5f),Reference-type string? members can take a Generator<string> directly, or use .OrDefault(p) for a chance of null:
Nickname = f => f.Internet.Username().OrDefault(.2f), // string? with 20% nullThe extension namespace must be imported
String, struct, date-time and collection transforms live in Forged.Core.Extensions. Remove the using and .Capitalize(), .Nullable(), .AsList(), .ToUtc() etc. won't resolve.
Email's second parameter is the local part generator
Despite its provider name, the second argument to Forge.Internet.Email feeds the username before the @. Pass a name generator to base email addresses on real names; the domain comes from EmailKind.
GUID kind decides ordering
f.Text.Guid() defaults to V4 (random). For time-ordered values (better index locality), use GuidGenerator.Kind.V7 explicitly: f.Text.Guid(GuidGenerator.Kind.V7).
Single Generator instances are reusable
Generators are immutable factories. The same configured generator can be assigned to several properties or called many times; every Generate() draws fresh values. Only Literal always returns the same value, and only Memo pins one value per Get() cycle.