4
\$\begingroup\$

I need to get from an array of booleans to a Flags enum.

Here's what my enum looks like:

[Flags]
public enum UserContactPreferences { None = 0, SMS = 1, Email = 2, Phone = 4 }

Here's what my parsing code looks like currently.

private UserContactPreferences GetContactPreferences(bool email, bool phone, bool sms)
{
    var contactByEmail = email ? UserContactPreferences.Email : UserContactPreferences.None;
    var contactByPhone = phone ? UserContactPreferences.Phone : UserContactPreferences.None;
    var contactBySms = sms ? UserContactPreferences.SMS : UserContactPreferences.None;

    return contactByEmail | contactByPhone | contactBySms;
}

That's horrible! I must be able to do better. Any ideas?

\$\endgroup\$

2 Answers 2

9
\$\begingroup\$

Well, you need some sort of mapping between the parameters and the enum values, so you can't actually avoid having three almost same pieces of code. But you can make your code shorter by using ifs and |= instead of ternary operators:

private UserContactPreferences GetContactPreferences(bool email, bool phone, bool sms)
{
    var preferences = UserContactPreferences.None;

    if (email)
        preferences |= UserContactPreferences.Email;
    if (phone)
        preferences |= UserContactPreferences.Phone;
    if (sms)
        preferences |= UserContactPreferences.SMS;

    return preferences;
}

If you had more parameters (though that would be a code smell), it might be worth considering something like using a dictionary. But I'm not sure it's a good idea, it's certainly longer than the previous version:

private UserContactPreferences GetContactPreferences(bool email, bool phone, bool sms)
{
    var dictionary = new Dictionary<UserContactPreferences, bool>
    {
        { UserContactPreferences.Email, email },
        { UserContactPreferences.Phone, phone },
        { UserContactPreferences.SMS, sms }
    };

    var preferences = UserContactPreferences.None;

    foreach (var kvp in dictionary)
    {
        if (kvp.Value)
            preferences |= kvp.Key;
    }

    return preferences;
}
\$\endgroup\$
1
  • \$\begingroup\$ Hmm... Of the two, I guess the |= solution works best for me. Thanks! \$\endgroup\$
    – NeilD
    Feb 5, 2013 at 17:10
3
\$\begingroup\$

You have used conditionals to parse multiple booleans into a Flags enum. Which is a fine, but can become quite project specific. Say you have a very big enum; you'll need to write nested conditionals for each combination of booleans. Doing so would make make the code unnecessarily verbose, as you would need to do that everywhere you want to convert sets of booleans into enums.

By bypassing type safety, casting to object, and assuming the flag enum is numbered sequentially we can reduce the verbosity. We can then use extension methods and generics to convert arrays of booleans into and from arbitrary enums. Instead of GetContactPreferences( false, true, false ) we can instead use new []{ false, true, false }.ToFlagEnum<UserContactPreferences>() to get the enum.

Converting the flag enum to a boolean array:

public static bool[] ToBoolArray<T>( this T @enum ) where T : Enum
{
    var values = ( int[] )Enum.GetValues( @enum.GetType() );
    var arr = new bool[ values.Length + 1 ];

    for ( var i = 0; i < arr.Length; i++ )
    {
        arr[ i ] = @enum.HasFlag( ( T )( object )( 1 << i ) );
    }

    return arr;
}

Convert a bool array to a flag enum:

public static T ToFlagEnum<T>( this bool[] arr ) where T : struct
{
    if ( typeof( T ).BaseType != typeof( Enum ) )
    {
        throw new TypeAccessException( "This extension method is for flag enums" );
    }

    var @enum = default( T );
    for ( var i = 0; i < arr.Length; i++ )
    {
        if ( arr[ i ] )
        {
            @enum = ( T )( object )( ( int )( object )@enum | ( 1 << i ) );
        }
    }

    return @enum;
}

If you wanted to, you could wrap it in your existing method to provide a familiar interface like this:

UserContactPreferences GetContactPreferences(bool email, bool phone, bool sms)
{
    return new []{true,email,phone,sms}.ToFlagEnum<UserContactPreferences>();
}
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.