从标志枚举中获取随机值

6
假设我有一个接受被标记 Flags 属性的枚举类型的函数。如果该枚举值是多个枚举元素的组合,那么如何随机提取其中一个元素?以下是我的尝试,但似乎应该有更好的方法。
[Flags]
enum Colours
{
    Blue = 1,
    Red = 2,
    Green = 4
}

public static void Main()
{
    var options = Colours.Blue | Colours.Red | Colours.Green;
    var opts = options.ToString().Split(',');
    var rand = new Random();
    var selected = opts[rand.Next(opts.Length)].Trim();
    var myEnum = Enum.Parse(typeof(Colours), selected);
    Console.WriteLine(myEnum);
    Console.ReadLine();
}
5个回答

11

您可以调用Enum.GetValues来获取枚举定义值的数组,代码如下:

var rand = new Random();

Colors[] allValues = (Colors[])Enum.GetValues(typeof(Colors));
Colors value = allValues[rand.Next(allValues.Length)];

抱歉,我之前没有表述清楚。我需要从枚举类型的一个子集中随机获取一个值,这个子集可以由按位运算符(如“蓝色 | 红色”)组合而成。 - Chris Porter

9
var options = Colours.Blue | Colours.Green;

var matching = Enum.GetValues(typeof(Colours))
                   .Cast<Colours>()
                   .Where(c => (options & c) == c)    // or use HasFlag in .NET4
                   .ToArray();

var myEnum = matching[new Random().Next(matching.Length)];

3
如果您不介意进行一些强制转换,并且您的枚举类型具有基本int类型,那么以下方法可以实现并且速度较快。
var rand = new Random();
const int mask = (int)(Colours.Blue | Colours.Red | Colours.Green);
return (Colours)(mask & (rand.Next(mask) + 1));

如果您只想设置单个标志,可以执行以下操作:

var rand = new Random();
return (Colours)(0x1 << (rand.Next(3)));

如果“(int)allValues&rand.Next()”的结果为零,并且您没有零枚举成员,则会得到无效的枚举,即(Colours)0。 - Tim Lloyd
好的,我认为现在它可以工作了。但是代码变得有点混乱和不太清晰。只有在性能是一个严重问题时才有用。 - bbudge
这个可以运行,但我只想要一个值返回。如果没有表达清楚,抱歉。你的解决方案返回了像“蓝色|红色|绿色”这样的组合。 - Chris Porter
已添加了代码,尽管有丑陋的“魔法”数字。有了这个,我完成了这个问题! - bbudge

2

如果我理解正确,这个问题是关于从标志枚举值中返回随机枚举值,而不是从标志枚举中返回随机成员。

    [Flags]
    private enum Shot
    {
        Whisky = 1,
        Absynthe = 2,
        Pochin = 4,
        BrainEraser = Whisky | Absynthe | Pochin
    }

    [Test]
    public void Test()
    {
        Shot myCocktail = Shot.Absynthe | Shot.Whisky;

        Shot randomShotInCocktail = GetRandomShotFromCocktail(myCocktail);
    }

    private static Shot GetRandomShotFromCocktail(Shot cocktail)
    {
        Random random = new Random();

        Shot[] cocktailShots = Enum.GetValues(typeof(Shot)).
           Cast<Shot>().
           Where(x => cocktail.HasFlag(x)).ToArray();

        Shot randomShot = cocktailShots[random.Next(0, cocktailShots.Length)];

        return randomShot;
    }

编辑

显然,您应该检查枚举是否是有效的值,例如:

 Shot myCocktail = (Shot)666;

Edit

Simplified


1
你在陈述问题时是正确的。不过我认为您的随机函数不会返回均匀分布的值。在每个循环中,每个值有50/50的机会被选择,因此后续的值被选中的概率较低。 - Chris Porter
我明白你的意思。严格来说,它仍然是随机的并且回答了你的问题。我已经更新为均匀分布。现在已经很晚了,我肯定可能搞砸了! :) - Tim Lloyd
你不是在寻找想要的答案吗? - Tim Lloyd
肯定是一个好答案,但我选择的答案稍微短一些,仅此而已。 - Chris Porter
2
不冒犯,但我认为你会发现在我回答之前,每个人都回答了这个问题错误。 - Tim Lloyd

-1
在我的情况下 - 我有一些缺少成员的枚举,例如0x01、0x02、0x08,以及具有最大值0x200000000的大型ulong枚举。
这段代码适用于我所有的情况:
/// <summary>
///     Gets <see cref="System.Random"/> instance.
/// </summary>
public static Random Random { get; } = new Random(Guid.NewGuid().GetHashCode());

/// <summary>
///     Gets random combination of Flags Enum.
/// </summary>
/// <typeparam name="T">Enum type.</typeparam>
/// <returns>Random Flags Enum combination.</returns>
public static T GetRandomFlagsEnumValue<T>()
    where T : Enum
{
    var allValues = (T[])Enum.GetValues(typeof(T));

    ulong numberValue = allValues.OrderBy(x => Random.Next())
        .Take(GetRandomInteger(1, allValues.Length - 1))
        .Select(e => Convert.ToUInt64(e, CultureInfo.InvariantCulture))
        .Aggregate((a, c) => a + c);

    return (T)Enum.ToObject(typeof(T), numberValue);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接