Frage

I have found good examples on how to create extension methods to read out single values from bitwise enums. But now that C# 4 has added the HasFlag method they are really not needed.

What I think would be really helpful though is an extension to SET a single flag!
I have many situations where I need to set the flag values individually.
I want an extension method with this signature:

enumVariable.SetFlag(EnumType.SingleFlag, true);

OR possibly:

enumVariable.SetFlag<EnumType>(EnumType.SingleFlag, true);
War es hilfreich?

Lösung

Today I found a solution on http://hugoware.net/blog/enums-flags-and-csharp. Thanks Hugo! Excellent code that works fine. I adjusted it slightly and added it to my existing EnumExtender:

public static class EnumExtender
{
    /// <summary>
    /// Adds a flag value to enum.
    /// Please note that enums are value types so you need to handle the RETURNED value from this method.
    /// Example: myEnumVariable = myEnumVariable.AddFlag(CustomEnumType.Value1);
    /// </summary>
    public static T AddFlag<T>(this Enum type, T enumFlag)
    {
        try
        {
            return (T)(object)((int)(object)type|(int)(object)enumFlag);
        }
        catch(Exception ex)
        {
            throw new ArgumentException(string.Format("Could not append flag value {0} to enum {1}",enumFlag, typeof(T).Name), ex);
        }
    }

    /// <summary>
    /// Removes the flag value from enum.
    /// Please note that enums are value types so you need to handle the RETURNED value from this method.
    /// Example: myEnumVariable = myEnumVariable.RemoveFlag(CustomEnumType.Value1);
    /// </summary>
    public static T RemoveFlag<T>(this Enum type, T enumFlag)
    {
        try
        {
            return (T)(object)((int)(object)type & ~(int)(object)enumFlag);
        }
        catch (Exception ex)
        {
            throw new ArgumentException(string.Format("Could not remove flag value {0} from enum {1}", enumFlag, typeof(T).Name), ex);
        }
    }

    /// <summary>
    /// Sets flag state on enum.
    /// Please note that enums are value types so you need to handle the RETURNED value from this method.
    /// Example: myEnumVariable = myEnumVariable.SetFlag(CustomEnumType.Value1, true);
    /// </summary>
    public static T SetFlag<T>(this Enum type, T enumFlag, bool value)
    {
        return value ? type.AddFlag(enumFlag) : type.RemoveFlag(enumFlag);
    }

    /// <summary>
    /// Checks if the flag value is identical to the provided enum.
    /// </summary>
    public static bool IsIdenticalFlag<T>(this Enum type, T enumFlag)
    {
        try
        {
            return (int)(object)type == (int)(object)enumFlag;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// Convert provided enum type to list of values.
    /// This is convenient when you need to iterate enum values.
    /// </summary>
    public static List<T> ToList<T>()
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException();
        var values = Enum.GetNames(typeof(T));
        return values.Select(value => value.ToEnum<T>()).ToList();
    }

    /// <summary>
    /// Present the enum values as a comma separated string.
    /// </summary>
    public static string GetValues<T>()
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException();
        var values = Enum.GetNames(typeof(T));
        return string.Join(", ", values);
    }

}

Andere Tipps

I've done something that works for me and very simple. Probably not efficient due to dynamic casting usage. But perhaps you could like it?

public static T SetFlag<T>(this Enum value, T flag, bool set)
{
    Type underlyingType = Enum.GetUnderlyingType(value.GetType());

    // note: AsInt mean: math integer vs enum (not the c# int type)
    dynamic valueAsInt = Convert.ChangeType(value, underlyingType);
    dynamic flagAsInt = Convert.ChangeType(flag, underlyingType);
    if (set)
    {
        valueAsInt |= flagAsInt;
    }
    else
    {
        valueAsInt &= ~flagAsInt;
    }

    return (T)valueAsInt;
}

I'm not sure what your question is here, but if you're asking if this is possible, I'd have to say that it isn't, not with this exact syntax.

Enums are value types, and as such, are passed by value. So a method, such as SetFlag, that receives an enum value will receive a COPY of it. Even if it sets a flag, that change would be confined to the method scope, not to the enum that it's called on.

You can pass it to a method with the ref modifier, like this: SetFlag(ref enumVariable, EnumType.SingleFlag) but this isn't supported as an extension method, as far as I know.

What you can do is either create a general enum helper class:

public static class EnumHelper
{
    public void SetFlag<TEnum>(ref TEnum enumValue, TEnum flag)
    {
         enumValue = enumValue | flag;
    }
}

or, alternately, create a SetFlag method that returns a new value rather than modifying the existing variable.

public static TEnum SetFlag<TEnum>(this TEnum enumValue, TEnum flag)
{
    return enumValue | flag;
}

Maybe not as pretty as you'd hoped but you can do it quite simply :)

enumVariable |= EnumType.SingleFlag;

You maybe need to implement the method for each enum because you can't constraint a enum this way:

public static T SetFlag<T>(this T @this, T flag, Boolean state) where T : enum { ... }

Anyway operator overloads are not allowed in C# on generic types, so you can't use the generic type T without casting.

Solution

So your extension methods must look like this:

public static MyFlag SetFlag(this MyFlag @this, MyFlag flag, Boolean state) 
{
    return state ? (@this | flag) : (@this & ~flag);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top