3

My Problem:

I want to convert my randomBloodType() method to a static method that can take any enum type. I want my method to take any type of enum whether it be BloodType, DaysOfTheWeek, etc. and perform the operations shown below.

Some Background on what the method does:

The method currently chooses a random element from the BloodType enum based on the values assigned to each element. An element with a higher value has a higher probability to be picked.

Code:

    public enum BloodType
    {
        // BloodType = Probability
        ONeg = 4,
        OPos = 36,
        ANeg = 3,
        APos = 28,
        BNeg = 1,
        BPos = 20,
        ABNeg = 1,
        ABPos = 5
    };

    public BloodType randomBloodType()
    {
        // Get the values of the BloodType enum and store it in a array
        BloodType[] bloodTypeValues = (BloodType[])Enum.GetValues(typeof(BloodType));
        List<BloodType> bloodTypeList = new List<BloodType>();

        // Create a list where each element occurs the approximate number of 
        // times defined as its value(probability)
        foreach (BloodType val in bloodTypeValues)
        {
            for(int i = 0; i < (int)val; i++)
            {
                bloodTypeList.Add(val);
            }
        }

        // Sum the values
        int sum = 0;
        foreach (BloodType val in bloodTypeValues)
        {
            sum += (int)val;
        }

        //Get Random value
        Random rand = new Random();
        int randomValue = rand.Next(sum);

        return bloodTypeList[randomValue];

    }

What I have tried so far:

I have tried to use generics. They worked out for the most part, but I was unable to cast my enum elements to int values. I included a example of a section of code that was giving me problems below.

    foreach (T val in bloodTypeValues)
    {
        sum += (int)val; // This line is the problem.
    }

I have also tried using Enum e as a method parameter. I was unable to declare the type of my array of enum elements using this method.

StenBone
  • 87
  • 8
  • return (T)(object)i.Value; // this might get the number value, I dunno – Jonathan Kittell Apr 26 '15 at 00:39
  • 2
    I can't help you'd be better off with a dictionary here. – Asad Saeeduddin Apr 26 '15 at 00:43
  • Do you want it to be an actual generic method? – krillgar Apr 26 '15 at 00:43
  • possible duplicate of [Create Generic method constraining T to an Enum](http://stackoverflow.com/questions/79126/create-generic-method-constraining-t-to-an-enum) – krillgar Apr 26 '15 at 00:53
  • _"I have tried to use generics...but I was unable to cast my enum elements to int values"_ -- please provide that code example, and be _specific_ about in what way you were "unable to cast" the values. A generic method should work fine; you won't be able to constrain the type parameter to ensure compile-time checking of callers, but as long as an actual `Enum` type is used, it should work. – Peter Duniho Apr 26 '15 at 01:07
  • 3
    You can't use an enum in this way. You cannot distinguish between `ABNeg` and `BNeg` because you've assigned them the same numeric value. – Ben Voigt Apr 26 '15 at 01:20
  • @Asad I have never used a dictionary before. What are it's advantages? – StenBone Apr 26 '15 at 16:19
  • @krillgar I am actually not sure if generics is the best solution. It was the first solution that popped in my head. I mainly want this method to be able to take in any enum and return a random element of that enum using each element's value as its probability. – StenBone Apr 26 '15 at 16:25
  • @PeterDuniho I updated my "What I have tried so far:" section with the block of code which is giving me problems when using generics. – StenBone Apr 26 '15 at 16:38
  • @krillgar Other than the code showing how to type check generics, I really didn't find the page([Create Generic method constraining T to an Enum](http://stackoverflow.com/questions/79126/create-generic-method-constraining-t-to-an-enum)) useful towards my problem. – StenBone Apr 26 '15 at 16:42
  • 1
    FYI: the answer you've accepted will not accomplish what you clearly are trying to do. See Ben Voigt's comment above; when you use the same weight for two different enum names, those enum names represent the same value. You can't pick them independently of each other. Frankly, while I'd love to see you mark/vote the answers in a way that makes sense, I am more concerned that you at least aren't using broken code. Please confirm that you understand why your proposed design won't work, and that you've taken a different approach. – Peter Duniho Apr 30 '15 at 02:40

3 Answers3

2

(Note: My apologies in advance for the lengthy answer. My actual proposed solution is not all that long, but there are a number of problems with the proposed solutions so far and I want to try to address those thoroughly, to provide context for my own proposed solution).


In my opinion, while you have in fact accepted one answer and might be tempted to use either one, neither of the answers provided so far are correct or useful.

Commenter Ben Voigt has already pointed out two major flaws with your specifications as stated, both related to the fact that you are encoding the enum value's weight in the value itself:

  1. You are tying the enum's underlying type to the code that then must interpret that type.
  2. Two enum values that have the same weight are indistinguishable from each other.

Both of these issues can be addressed. Indeed, while the answer you accepted (why?) fails to address the first issue, the one provided by Dweeberly does address this through the use of Convert.ToInt32() (which can convert from long to int just fine, as long as the values are small enough).

But the second issue is much harder to address. The answer from Asad attempts to address this by starting with the enum names and parsing them to their values. And this does indeed result in the final array being indexed containing the corresponding entries for each name separately. But the code actually using the enum has no way to distinguish the two; it's really as if those two names are a single enum value, and that single enum value's probability weight is the sum of the value used for the two different names.

I.e. in your example, while the enum entries for e.g. BNeg and ABNeg will be selected separately, the code that receives these randomly selected value has no way to know whether it was BNeg or ABNeg that was selected. As far as it knows, those are just two different names for the same value.

Now, even this problem can be addressed (but not in the way that Asad attempts to…his answer is still broken). If you were, for example, to encode the probabilities in the value while still ensuring unique values for each name, you could decode those probabilities while doing the random selection and that would work. For example:

enum BloodType
{
    // BloodType = Probability
    ONeg = 4 * 100 + 0,
    OPos = 36 * 100 + 1,
    ANeg = 3 * 100 + 2,
    APos = 28 * 100 + 3,
    BNeg = 1 * 100 + 4,
    BPos = 20 * 100 + 5,
    ABNeg = 1 * 100 + 6,
    ABPos = 5 * 100 + 7,
};

Having declared your enum values that way, then you can in your selection code divide the enum value by 100 to obtain its probability weight, which then can be used as seen in the various examples. At the same time, each enum name has a unique value.

But even solving that problem, you are still left with problems related to the choice of encoding and representation of the probabilities. For example, in the above you cannot have an enum that has more than 100 values, nor one with weights larger than (2^31 - 1) / 100; if you want an enum that has more than 100 values, you need a larger multiplier but that would limit your weight values even more.

In many scenarios (maybe all the ones you care about) this won't be an issue. The numbers are small enough that they all fit. But that seems like a serious limitation in what seems like a situation where you want a solution that is as general as possible.

And that's not all. Even if the encoding stays within reasonable limits, you have another significant limit to deal with: the random selection process requires an array large enough to contain for each enum value as many instances of that value as its weight. Again, if the values are small maybe this is not a big problem. But it does severely limit the ability of your implementation to generalize.


So, what to do?

I understand the temptation to try to keep each enum type self-contained; there are some obvious advantages to doing so. But there are also some serious disadvantages that result from that, and if you truly ever try to use this in a generalized way, the changes to the solutions proposed so far will tie your code together in ways that IMHO negate most if not all of the advantage of keeping the enum types self-contained (primarily: if you find you need to modify the implementation to accommodate some new enum type, you will have to go back and edit all of the other enum types you're using…i.e. while each type looks self-contained, in reality they are all tightly coupled with each other).

In my opinion, a much better approach would be to abandon the idea that the enum type itself will encode the probability weights. Just accept that this will be declared separately somehow.

Also, IMHO is would be better to avoid the memory-intensive approach proposed in your original question and mirrored in the other two answers. Yes, this is fine for the small values you're dealing with here. But it's an unnecessary limitation, making only one small part of the logic simpler while complicating and restricting it in other ways.

I propose the following solution, in which the enum values can be whatever you want, the enum's underlying type can be whatever you want, and the algorithm uses memory proportionally only to the number of unique enum values, rather than in proportion to the sum of all of the probability weights.

In this solution, I also address possible performance concerns, by caching the invariant data structures used to select the random values. This may or may not be useful in your case, depending on how frequently you will be generating these random values. But IMHO it is a good idea regardless; the up-front cost of generating these data structures is so high that if the values are selected with any regularity at all, it will begin to dominate the run-time cost of your code. Even if it works fine today, why take the risk? (Again, especially given that you seem to want a generalized solution).

Here is the basic solution:

static T NextRandomEnumValue<T>()
{
    KeyValuePair<T, int>[] aggregatedWeights = GetWeightsForEnum<T>();
    int weightedValue =
            _random.Next(aggregatedWeights[aggregatedWeights.Length - 1].Value),

        index = Array.BinarySearch(aggregatedWeights,
            new KeyValuePair<T, int>(default(T), weightedValue),
            KvpValueComparer<T, int>.Instance);

    return aggregatedWeights[index < 0 ? ~index : index + 1].Key;
}

static KeyValuePair<T, int>[] GetWeightsForEnum<T>()
{
    object temp;

    if (_typeToAggregatedWeights.TryGetValue(typeof(T), out temp))
    {
        return (KeyValuePair<T, int>[])temp;
    }

    if (!_typeToWeightMap.TryGetValue(typeof(T), out temp))
    {
        throw new ArgumentException("Unsupported enum type");
    }

    KeyValuePair<T, int>[] weightMap = (KeyValuePair<T, int>[])temp;
    KeyValuePair<T, int>[] aggregatedWeights =
        new KeyValuePair<T, int>[weightMap.Length];
    int sum = 0;

    for (int i = 0; i < weightMap.Length; i++)
    {
        sum += weightMap[i].Value;
        aggregatedWeights[i] = new KeyValuePair<T,int>(weightMap[i].Key, sum);
    }

    _typeToAggregatedWeights[typeof(T)] = aggregatedWeights;

    return aggregatedWeights;
}

readonly static Random _random = new Random();

// Helper method to reduce verbosity in the enum-to-weight array declarations
static KeyValuePair<T1, T2> CreateKvp<T1, T2>(T1 t1, T2 t2)
{
    return new KeyValuePair<T1, T2>(t1, t2);
}

readonly static KeyValuePair<BloodType, int>[] _bloodTypeToWeight =
{
    CreateKvp(BloodType.ONeg, 4),
    CreateKvp(BloodType.OPos, 36),
    CreateKvp(BloodType.ANeg, 3),
    CreateKvp(BloodType.APos, 28),
    CreateKvp(BloodType.BNeg, 1),
    CreateKvp(BloodType.BPos, 20),
    CreateKvp(BloodType.ABNeg, 1),
    CreateKvp(BloodType.ABPos, 5),
};

readonly static Dictionary<Type, object> _typeToWeightMap =
    new Dictionary<Type, object>()
    {
        { typeof(BloodType), _bloodTypeToWeight },
    };

readonly static Dictionary<Type, object> _typeToAggregatedWeights =
    new Dictionary<Type, object>();

Note that the work of actually selecting a random value is simply a matter of choosing a non-negative random integer less than the sum of the weights, and then using a binary search to find the appropriate enum value.

Once per enum type, the code will build the table of values and weight-sums that will be used for the binary search. This result is stored in a cache dictionary, _typeToAggregatedWeights.

There are also the objects that have to be declared and which will be used at run-time to build this table. Note that the _typeToWeightMap is just in support of making this method 100% generic. If you wanted to write a different named method for each specific type you wanted to support, that could still used a single generic method to implement the initialization and selection, but the named method would know the correct object (e.g. _bloodTypeToWeight) to use for initialization.

Alternatively, another way to avoid the _typeToWeightMap while still keeping the method 100% generic would be to have the _typeToAggregatedWeights be of type Dictionary<Type, Lazy<object>>, and have the values of the dictionary (the Lazy<object> objects) explicitly reference the appropriate weight array for the type.

In other words, there are lots of variations on this theme that would work fine. But they will all have essentially the same structure as above; semantics would be the same and performance differences would be negligible.

One thing you'll notice is that the binary search requires a custom IComparer<T> implementation. That is here:

class KvpValueComparer<TKey, TValue> :
    IComparer<KeyValuePair<TKey, TValue>> where TValue : IComparable<TValue>
{
    public readonly static KvpValueComparer<TKey, TValue> Instance =
        new KvpValueComparer<TKey, TValue>();

    private KvpValueComparer() { }

    public int Compare(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
    {
        return x.Value.CompareTo(y.Value);
    }
}

This allows the Array.BinarySearch() method to correct compare the array elements, allowing a single array to contain both the enum values and their aggregated weights, but limiting the binary search comparison to just the weights.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • This doesn't solve the problem presented in the question, and solves a different problem entirely. – Asad Saeeduddin Apr 27 '15 at 01:26
  • _"This doesn't solve the problem presented in the question"_ -- of course it does. To claim otherwise is ridiculous. The stated problem is clear: from a collection of enum values, with corresponding weights, randomly choose a value based on those weights. That the OP presented a broken implementation is no excuse for giving them a broken answer, and to claim that a solution that **actually works** is somehow "solving a different problem entirely" is missing the point at best and outright ignorant at worst. – Peter Duniho Apr 27 '15 at 04:25
  • The problem asks for how you can select a random enum value weighted by the value itself. This answer doesn't provide code that solves that problem, therefore it doesn't solve the problem. Simple, really. – Asad Saeeduddin Apr 27 '15 at 04:30
  • _"The problem asks for how you can select a random enum value weighted by the value itself"_ -- no, it doesn't. All the question does is show that that's how the OP _tried_ to solve the problem so far. There's nothing in the question that says that's how an answer _must_ work, and in fact it is **impossible** for it to do so (though that didn't stop you from trying, and then irrationally defending that attempt). That you think that's what the question asks simply proves my point. Thank you for your support. – Peter Duniho Apr 27 '15 at 04:39
  • I mean you can put as much emphasis as you like on the word "impossible", but the fact remains that you can select a value at random from an enum, weighted by the value itself. The code above already does that. The fact that when you're casting back to enum, the string representation is always the label declared first, is irrelevant to the problem of selecting a value at random according to the desired distribution. You're welcome btw :) – Asad Saeeduddin Apr 27 '15 at 04:45
  • 2
    _"you can select a value at random from an enum, weighted by the value itself"_ -- no, you can't. At best, if you mean _literally_ "the value" and not the actual enum name, for values that are represented multiple times, their weights are multiplied by the number of incidences, so they are _not_ weighted by their value. And it's painfully obvious in the OP's case that he really wants to treat different enum names as different values, which is simply **impossible** (yes, again) if the names have the same values assigned to them. – Peter Duniho Apr 27 '15 at 05:11
  • "their weights are multiplied by the number of incidences," this is a quirk in my implementation, but there is no reason for this to be necessarily so. – Asad Saeeduddin Apr 27 '15 at 05:24
  • _"there is no reason for this to be necessarily so"_ -- sure there is, unless you interpret the multiple incidences as irrelevant and ignore the extra ones (e.g. throw a `Distinct()` in there too). And doing so would blindly ignore the fact that the OP clearly wants to choose different names, even when they have the same weight. Wait...you don't seriously believe that he really intends e.g. that the `BNeg` enum name should never be chosen, just because it has the same weight as `ABNeg`, do you? Or looked at a different way, that he really intends to choose two different names at once? – Peter Duniho Apr 27 '15 at 05:58
  • `Dictionary` iterates just like `KeyValuePair[]` and also supports a collection initializer. You can even call LINQ's `ToArray()` on the dictionary if you really want an array for memory locality. But you seem to be using Dictionary already. – Ben Voigt May 03 '15 at 16:12
  • Actually, the preprocessing step could/should accept a dictionary (perhaps via `IEnumerable>)`, and create an optimized array. – Ben Voigt May 03 '15 at 16:16
  • @BenVoigt: I think I see what you mean. But my choice to do it this way was intentional, to keep the code as clear as possible (and granted, at the possible expense of conciseness). (I was even tempted to use `Tuple` instead of `KeyValuePair`, just to make sure there was no confusion about why I was using KVP...i.e. it didn't have anything to do with dictionaries...but wound up too emotionally attached to the `struct`-ness of KVP :) ). – Peter Duniho May 03 '15 at 16:47
0

Assuming your enum values are all of type int (you can adjust this accordingly if they're long, short, or whatever):

static TEnum RandomEnumValue<TEnum>(Random rng)
{
    var vals = Enum
        .GetNames(typeof(TEnum))
        .Aggregate(Enumerable.Empty<TEnum>(), (agg, curr) =>
        {
            var value = Enum.Parse(typeof (TEnum), curr);
            return agg.Concat(Enumerable.Repeat((TEnum)value,(int)value)); // For int enums
        })
        .ToArray();

    return vals[rng.Next(vals.Length)];
}

Here's how you would use it:

var rng = new Random();
var randomBloodType = RandomEnumValue<BloodType>(rng);

People seem to have their knickers in a knot about multiple indistinguishable enum values in the input enum (for which I still think the above code provides expected behavior). Note that there is no answer here, not even Peter Duniho's, that will allow you to distinguish enum entries when they have the same value, so I'm not sure why this is being considered as a metric for any potential solutions.

Nevertheless, an alternative approach that doesn't use the enum values as probabilities is to use an attribute to specify the probability:

public enum BloodType
{
    [P=4]
    ONeg,
    [P=36]
    OPos,
    [P=3]
    ANeg,
    [P=28]
    APos,
    [P=1]
    BNeg,
    [P=20]
    BPos,
    [P=1]
    ABNeg,
    [P=5]
    ABPos
}

Here is what the attribute used above looks like:

[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class PAttribute : Attribute
{
    public int Weight { get; private set; }

    public PAttribute(int weight)
    {
        Weight = weight;
    }
}

and finally, this is what the method to get a random enum value would like:

static TEnum RandomEnumValue<TEnum>(Random rng)
{
    var vals = Enum
        .GetNames(typeof(TEnum))
        .Aggregate(Enumerable.Empty<TEnum>(), (agg, curr) =>
        {
            var value = Enum.Parse(typeof(TEnum), curr);

            FieldInfo fi = typeof (TEnum).GetField(curr);
            var weight = ((PAttribute)fi.GetCustomAttribute(typeof(PAttribute), false)).Weight;

            return agg.Concat(Enumerable.Repeat((TEnum)value, weight)); // For int enums
        })
        .ToArray();

    return vals[rng.Next(vals.Length)];
}

(Note: if this code is performance critical, you might need to tweak this and add caching for the reflection data).

Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
  • @BenVoigt That's odd. Works fine in .net 4.5 on VS 2013. – Asad Saeeduddin Apr 26 '15 at 01:17
  • 1
    Oh, I see, you've said it needs to be changed by the base type of the enum. That kind of defeats the purpose of a generic. – Ben Voigt Apr 26 '15 at 01:18
  • @BenVoigt I'm using generics not for any compile time safety benefits (reflection is unavoidable here) but because I like the syntax better than passing in `typeof` whatever enum. YMMV – Asad Saeeduddin Apr 26 '15 at 01:22
  • @Asad Thank you for this awesome solution. It took me awhile to understand what was going on because this is the first time I have really been exposed to lambda functions, enumerables and the Aggregate function in C#. Your solution is great and I will be studying it for some time. – StenBone Apr 26 '15 at 17:51
  • I do have one question. I tried adding the code below to the beginning of the method to check what was getting passed in as a generic. The problem is that the code triggers even though I am passing my BloodType enum in as the generic. Does anyone know why this is occuring?`if (!typeof(TEnum).IsEnum) { throw new ArgumentException("T must be an enumerated type"); }` – StenBone Apr 26 '15 at 18:36
  • @BloodHound27 That's odd. I can't reproduce your issue. If I add that in the method and call it with your `BloodType` enum, no exception occurs. – Asad Saeeduddin Apr 26 '15 at 18:54
  • @Asad That is very interesting. I think it is because I am not running the code in a traditional environment. I am actually running the code as a mod for the video game Space Engineers. It must be a problem on the Space Engineers end with how they run and compile mods. – StenBone Apr 26 '15 at 19:42
  • Note: no implementation that is supposed to select a single random item, and which starts out with `var rng = new Random();`, could be considered a good solution, as such implementations are not in fact random. – Peter Duniho Apr 26 '15 at 20:49
  • @PeterDuniho They are in fact random. The default `Random` constructor is seeded by the current time, so you'll get different values each time you call the method. It may not follow a statistical distribution (you'd need to pass in the appropriate rng for that) but it definitely is (pseudo) random – Asad Saeeduddin Apr 26 '15 at 21:45
  • @Asad: "seeded by the current time" is is the exact opposite of random. First, if you call the method too frequently, you will get the same value repeatedly, because the time value used to seed the PRNG hasn't had time to change (the CPU executes instructions orders of magnitude more quickly than the tick counter is updated). Second, even if you get a different seed each time, the seed is _correlated_ with the clock. By definition, if your number is correlated with some outside value, it's not random. – Peter Duniho Apr 26 '15 at 21:58
  • @PeterDuniho Yeah, didn't think about the clock resolution. Will update the code. – Asad Saeeduddin Apr 26 '15 at 22:03
  • The solution is still broken, because it's based on the OP's code which never did resolve the problem that two enum values with the same probability weight are indistinguishable from each other. You do select different names separately, but the caller has no way to tell the two apart once it receives the value (i.e. you return the value `1` whether you select `BNeg` or `ABNeg`...as far as the caller is concerned, you've selected _both_ `BNeg` and `ABNeg`, because those are just two different names for the same value). – Peter Duniho Apr 26 '15 at 23:31
  • @PeterDuniho I see what you mean now. But this is a problem with the pathological input; i.e. there are two names for the same value. The output is still correct; it picks a random value from the enum, weighted by value. I don't see why you would downvote my answer for that. – Asad Saeeduddin Apr 26 '15 at 23:44
  • 2
    _"But this is a problem with the pathological input; i.e. there are two names for the same value"_ -- there is nothing "pathological" about two different enum names having the same _weight_. That's a perfectly plausible and reasonable input; it is pathological only because the OP, and subsequently you, have chosen a **broken** implementation. Your answer, rather than addressing the _actual_ need the OP has, focuses on continuing their broken approach, which is not useful at all. I.e. it is not possible to successfully use your answer in the OP's scenario. – Peter Duniho Apr 27 '15 at 04:21
-1

Some of this you can do and some of it isn't so easy. I believe the following extension method will do what you describe.

static public class Util {
    static Random rnd = new Random();
    static public int PriorityPickEnum(this Enum e) {
        // The approved types for an enum are byte, sbyte, short, ushort, int, uint, long, or ulong
        // However, Random only supports a int (or double) as a max value.  Either way
        // it doesn't have the range for uint, long and ulong.
        //
        // sum enum 
        int sum = 0;
        foreach (var x in Enum.GetValues(e.GetType())) {
            sum += Convert.ToInt32(x);
            }

        var i = rnd.Next(sum); // get a random value, it will form a ratio i / sum

        // enums may not have a uniform (incremented) value range (think about flags)
        // therefore we have to step through to get to the range we want,
        // this is due to the requirement that return value have a probability
        // proportional to it's value.  Note enum values must be sorted for this to work.
        foreach (var x in Enum.GetValues(e.GetType()).OfType<Enum>().OrderBy(a => a)) {
            i -= Convert.ToInt32(x);
            if (i <= 0) return Convert.ToInt32(x);
            }
        throw new Exception("This doesn't seem right");
        }
    }

Here is an example of using this extension:

        BloodType bt = BloodType.ABNeg;
        for (int i = 0; i < 100; i++) {
            var v = (BloodType) bt.PriorityPickEnum();
            Console.WriteLine("{0}:  {1}({2})", i, v, (int) v);
            }

This should work pretty well for enum's of type byte, sbyte, ushort, short and int. Once you get beyond int (uint, long, ulong) the problem is the Random class. You can adjust the code to use doubles generated by Random, which would cover uint, but the Random class just doesn't have the range to cover long and ulong. Of course you could use/find/write a different Random class if this is important.

Dweeberly
  • 4,668
  • 2
  • 22
  • 41
  • 1
    You get invalid cast exceptions at runtime for enums with underlying type other than `int` or `uint` -- http://rextester.com/GGGJQ16552 – Ben Voigt Apr 26 '15 at 16:47
  • I see, how annoying. I've updated the code to correct that issue. You could change the Convert.ToInt32 to Convert.ToInt64 and pick up the longs, but you still end up with the range issue. – Dweeberly Apr 26 '15 at 19:27
  • I didn't bother to debug this thoroughly, but it definitely doesn't work. You have the same fundamental problem as the OP's code and Asad's proposal, which is that the declared enum type just has no way to distinguish e.g. between `BNeg` and `ABNeg`. The demo code is wrong too, as the compile-time type of `v` is `int` so the output doesn't show enum value names. You should cast to `BloodType` somewhere. – Peter Duniho Apr 26 '15 at 23:40
  • @PeterDuniho I didn't take that BNeg and ABNeg were the heart of the issue/question here, and Asad's response to you above reinforced that. It is a validate point, but according to the docs:"If multiple enumeration members have the same underlying value, ..., your application code should never depend on the method returning a particular member's name." https://msdn.microsoft.com/en-us/library/system.enum.getname.aspx. As to casting 'v' to BloodType I think you have not looked at the code. As to it not working I think you did not run the code. – Dweeberly Apr 27 '15 at 19:23
  • My point (as in the other comments) is that the OP's original proposal simply **does not work** and cannot work. It does no good to provide code that solves the basic syntactical issues without resolving the more fundamental problem that the idea of overloading the enum values to represent the weights as well is just plain broken. As for the casting, you are right...I overlooked that I had changed your code subtly when testing it, and wound up losing the cast in the process. I apologize for the confusion. – Peter Duniho Apr 27 '15 at 20:31
  • I agree with your assessment but not your conclusion. I believe I could modify the code I've given to create a dictionary> which would map the Enum values to lists of Enum names. After choosing a value I could then choose (randomly) one of the names in the list. However, the documentation indicates that that might already be happening. In any case it I didn't feel this additional complexity improved the answer. If you think it would be helpful to others, I'm happy to add such code to my answer; and perhaps a short discussion of possible negative performance impact. – Dweeberly Apr 27 '15 at 21:25