应复用 random 实例或使用 random.shared;取不重复随机数优先洗牌(fisher-yates 更优),避免边生成边查重;动态场景用 hashset 高效去重。

用 Random 生成基本随机数,但别在循环里反复 new
直接调用 new Random().Next() 看似简单,实际在高频调用(比如 for 循环内)会导致大量重复值——因为 Random 默认用系统时钟做种子,短时间多次实例化会拿到相同种子,从而产出相同序列。
正确做法是复用一个 Random 实例:
private static readonly Random _random = new Random();
如果需要线程安全(比如多线程并发取随机数),改用 Random.Shared(.NET 6+):
int value = Random.Shared.Next(1, 101); // [1, 101)
-
Next():返回非负整数(0 到int.MaxValue) -
Next(int max):[0, max) -
Next(int min, int max):[min, max),注意 max 不包含
要不重复的随机数?别边生成边查重,用洗牌更稳
“生成一个、检查是否已存在、重复就重试”这种逻辑在范围小、数量大时极易卡死(比如从 1–10 中取 9 个不重复数,最后几个可能反复碰撞)。更可靠的方式是先构造全集,再打乱顺序,然后截取。
例如取 5 个 1–20 之间不重复的随机整数:
var numbers = Enumerable.Range(1, 20).OrderBy(_ => Random.Shared.Next()).Take(5).ToArray();
这个写法简洁,但要注意:OrderBy 基于随机值排序,虽够用,但不是严格均匀洗牌(Fisher-Yates 更优)。如对统计分布敏感,应手写洗牌:
var list = Enumerable.Range(1, 20).ToList();
for (int i = list.Count - 1; i > 0; i--)
{
int j = Random.Shared.Next(i + 1);
(list[i], list[j]) = (list[j], list[i]);
}
var result = list.Take(5).ToArray();
用 HashSet<int></int> 适合「动态决定取多少」的场景
当你要持续生成、直到满足某个条件(比如“生成直到和大于 100 的不重复随机数”),或无法预知要多少个时,HashSet 比预分配数组更自然:
var seen = new HashSet<int>(); while (seen.Count <ul> <li>插入去重是 O(1) 平均复杂度,比 List.Contains 快得多</li> <li>不保证顺序,如需升序/降序得额外调用 <code>OrderBy</code> </li> <li>注意:<code>HashSet.Add()</code> 返回 <code>bool</code>,可用来判断是否新增成功</li> </ul> <h3>小心 <code>Random</code> 在 Unity 或 ASP.NET Core 中的陷阱</h3> <p>Unity 早期版本中 <code>Random</code> 是静态类(<code>UnityEngine.Random</code>),和 <code>System.Random</code> 完全无关,API 也不同(比如用 <code>Range(min, max)</code>)。混用会导致编译通过但行为异常。</p> <p>ASP.NET Core 中若把 <code>Random</code> 实例注册为 Singleton 服务,要注意它不是线程安全的(<code>Random.Shared</code> 才是)。.NET 5 及以前必须加锁或换用 <code>ThreadLocal<random></random></code>。</p> <p>最省心的底线方案:只要不是性能关键路径,一律用 <code>Random.Shared</code>(.NET 6+)或封装一层带锁的静态实例(旧框架)。</p> <p>真正容易被忽略的是边界语义——<code>Next(1, 10)</code> 产生的是 1 到 9,不是 1 到 10;而 <code>NextDouble()</code> 返回 [0.0, 1.0),这些半开区间在做映射时稍不注意就会偏一格。</p></int>











