I have Enum:
public enum SomeType
{
TypeA,
TypeB,
TypeC
}
but in MongoDB i would like this map to:
type_a type_b type_c
I'm using EnumRepresentationConvention(BsonType.String)
I tried:
public enum SomeType
{
[BsonElement("type_a")]
TypeA,
[BsonElement("type_b")]
TypeB,
[BsonElement("type_c")]
TypeC
}
but this doesn't work. Im getting exception:
Requested value 'type_a' was not found.
Is anyone know how can achieve such mapping in MongoDb C# driver?
UPDATE
So I wrote a new serializer that does what you need. I built it off some code I wrote as part of SharpExtensions. It certainly isn't optimized (or as simplified as it could be) but it works.
First I created a sample class Foo and reused your example Enum. I then leveraged the DescriptionAttribute to specify an alternate representation of the Enum that you completely control. Though this could be potentially simplified if you leverage something like Humanizer to consistently change the representation.
I then created a BsonSerializationProvider to let the driver know when it should use that serializer (much like my original answer). The meat is in EnumDescriptionSerializer which uses reflection to find the string representation of a particular value of SomeType. This is where I leverage the boilerplate code from SharpExtensions to move between the string and the actual Enum value. You'll notice the code will also work with EnumMemberAttribute as well as DescriptionAttribute. Please feel free to import the SharpExtensions library if you don't want to use the boilerplate code directly.
public class Foo
{
public ObjectId Id {get;set;}
public SomeType Enum {get;set;}
}
public enum SomeType
{
[Description("type_a")]
TypeA,
[Description("type_b")]
TypeB,
[Description("type_c")]
TypeC
}
public class EnumDescriptionSerializerProvider : BsonSerializationProviderBase
{
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry registry)
{
if (!type.GetTypeInfo().IsEnum) return null;
var enumSerializerType = typeof(EnumDescriptionSerializer<>).MakeGenericType(type);
var enumSerializerConstructor = enumSerializerType.GetConstructor(new Type[0]);
var enumSerializer = (IBsonSerializer)enumSerializerConstructor?.Invoke(new object[0]);
return enumSerializer;
}
}
public class EnumDescriptionSerializer<TEnum> : StructSerializerBase<TEnum> where TEnum : struct
{
public BsonType Representation => BsonType.String;
public override TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var valAsString = context.Reader.ReadString();
var enumValue = valAsString.GetValueFromDescription<TEnum>();
return enumValue;
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnum value)
{
context.Writer.WriteString(value.GetDescription());
}
}
public static class EnumExtensions
{
public enum StringCase
{
/// <summary>
/// The default capitalization
/// </summary>
Default,
/// <summary>
/// Lower Case, ex. i like widgets.
/// </summary>
[Description("Lower Case")]
Lower,
/// <summary>
/// Upper Case, ex. I LIKE WIDGETS.
/// </summary>
[Description("Upper Case")]
Upper,
/// <summary>
/// Lower Camelcase, ex: iLikeWidgets.
/// </summary>
[Description("Lower Camelcase")]
LowerCamel,
/// <summary>
/// Upper Camelcase, ex: ILikeWidgets.
/// </summary>
[Description("Upper Camelcase")]
UpperCamel
}
/// <summary>
/// Get the value of an enum as a string.
/// </summary>
/// <param name="val"> The enum to convert to a <see cref="string"/>. </param>
/// <param name="case"> A <see cref="StringCase"/> indicating which case to return. Valid enumerations are StringCase.Lower and StringCase.Upper. </param>
/// <exception cref="ArgumentNullException"> If the enum is null. </exception>
/// <returns></returns>
public static string GetName<TEnum>(this TEnum val, StringCase #case = StringCase.Default) where TEnum : struct
{
var name = Enum.GetName(val.GetType(), val);
if (name == null) return null;
switch (#case)
{
case StringCase.Lower:
return name.ToLower();
case StringCase.Upper:
return name.ToUpper();
default:
return name;
}
}
/// <summary>
/// Gets the description for the supplied Enum Value.
/// </summary>
/// <param name="val">The value for which to get the description attribute.</param>
/// <returns>The <see cref="string"/> description.</returns>
public static string GetDescription<TEnum>(this TEnum val) where TEnum : struct
{
var fields = val.GetType().GetTypeInfo().GetDeclaredField(GetName(val));
// first try and pull out the EnumMemberAttribute, common when using a JsonSerializer
if (fields.GetCustomAttributes(typeof(EnumMemberAttribute), false).FirstOrDefault() is EnumMemberAttribute jsonAttribute) return jsonAttribute.Value;
// If that doesn't work, do the regular description, that still fails, just return a pretty ToString().
return !(fields.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() is DescriptionAttribute attribute) ? GetName(val) : attribute.Description;
}
/// <summary>
/// Get the value of an <see cref="Enum"/> based on its description attribute.
/// </summary>
/// <typeparam name="T">The type of the <see cref="Enum"/>.</typeparam>
/// <param name="description">The Description attribute of the <see cref="Enum"/>.</param>
/// <returns>The value of T or default(T) if the description is not found.</returns>
public static T GetValueFromDescription<T>(this string description) where T : struct
{
if (string.IsNullOrWhiteSpace(description)) throw new ArgumentNullException(nameof(description));
var type = typeof(T);
if (!type.GetTypeInfo().IsEnum) throw new ArgumentOutOfRangeException(nameof(T), $"{typeof(T)} is not an Enum.");
var fields = type.GetRuntimeFields();
foreach (var field in fields)
{
if (field.Name == description) return (T)field.GetValue(null);
// first try and pull out the EnumMemberAttribute, common when using a JsonSerializer
if (field.GetCustomAttribute(typeof(EnumMemberAttribute), false) is EnumMemberAttribute jsonAttribute && jsonAttribute.Value == description) return (T)field.GetValue(null);
// If that doesn't work, do the regular description, that still fails, just return a pretty ToString().
if (field.GetCustomAttribute(typeof(DescriptionAttribute), false) is DescriptionAttribute attribute && attribute.Description == description) return (T)field.GetValue(null);
}
throw new Exception($"Failed to parse value {description} into enum {typeof(T)}");
}
}
I wrote a simple test inserti
ng several Foo documents into a collection. This is how they look in the database
> db.enum.find()
{ "_id" : ObjectId("5c76c0240bba918778cc6b7f"), "Enum" : "type_a" }
{ "_id" : ObjectId("5c76c0580bba918778cc6b80"), "Enum" : "type_a" }
{ "_id" : ObjectId("5c76c05d0bba918778cc6b81"), "Enum" : "type_b" }
I also verified that they round-trip correctly. I haven't run any tests beyond some simple code using LINQPad. I believe this is what you are looking for.
ORIGINAL ANSWER
I wrote a custom serializer for this so I could register it and things "just work".
public class EnumAsStringSerializationProvider : BsonSerializationProviderBase
{
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry registry)
{
if (!type.GetTypeInfo().IsEnum) return null;
var enumSerializerType = typeof(EnumSerializer<>).MakeGenericType(type);
var enumSerializerConstructor = enumSerializerType.GetConstructor(new[] { typeof(BsonType) });
var enumSerializer = (IBsonSerializer) enumSerializerConstructor?.Invoke(new object[] { BsonType.String });
return enumSerializer;
}
}
Then I register it with the BsonSerializer.
var enumAsStringSerializationProvider = new EnumAsStringSerializationProvider();
BsonSerializer.RegisterSerializationProvider(enumAsStringSerializationProvider);
For me, it just works and I don't need to remember to decorate enums.
Related
I have a Dictionary containing strings as keys, and objects as values in an abstract class.
I have two classes deriving from this abstract class.
One of the deriving classes works perfectly, all configurations and items are loaded and retrievable without issues.
However, the other class is giving me headaches.
When I try to get an object of type "Domain"; I get an invalid cast exception, although I am adding the value to the dictionary as said type.
Here is the code:
public sealed class UserDomainConfig: ConfigParser {
public UserDomainConfig(string configFilePath) : base(configFilePath) { }
public Domain GetConfig(string key) => GetConfig<Domain>(key);
public override bool LoadConfigs() {
return base.LoadConfigs();
}
public UserDomainConfig SetConfig(string key, Domain value) {
base.SetConfig(key, value);
return this;
}
}
public abstract class ConfigParser: IConfig {
/* Snip */
/// <summary>
/// Gets the config.
/// </summary>
/// <returns>The config.</returns>
/// <param name="key">Key.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public virtual T GetConfig<T>(string key) {
object output = null;
try {
if (!configs.TryGetValue(key, out output))
return default(T);
//return (T)output;
//return output as T;
// This is where the exception is occurring.
// I've tried multiple solutions to try to remedy this issue.
return (T)Convert.ChangeType(output, typeof(T));
} catch (InvalidCastException ex) {
logger.Error($"Failed to cast config { key }!");
}
return default(T);
}
/// <summary>
/// Sets the config.
/// </summary>
/// <returns>The config.</returns>
/// <param name="key">Key.</param>
/// <param name="value">Value.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public virtual IConfig SetConfig<T>(string key, T value) {
if (KeyExists(key))
configs.Remove(key);
configs.Add(key, value);
return this;
}
Any ideas on how to fix this, and/or why this isn't working in the first place, although it works like a charm with strings, bools, and ints?
The Convert class only supports simple types, known by .NET, like Int32, String, DateTime. It does not support user defined or complex types like Domain. If you try to convert something to a not-supported type, the method Convert.ChangeType throws an InvalidCastException. The only exception is that it will work if the Original value (output) is already of that desired type; than no actual conversion is needed.
For more information, read: https://msdn.microsoft.com/en-us/library/dtb69x08(v=vs.110).aspx
If you are certain the stored value is of the type Domain, than log more information. Something like this:
logger.Error($"Failed to cast config { key } of type { output.GetType() } to type { typeof(T) }!");
This way you can verify your claim that both types are the same.
WCF supports using enum types that are tagged with the FlagsAttribute as parameters within an UriTemplate. Like this:
[DataContract]
[Flags]
public enum OptionsEnum
{
[EnumMember]
None = 0,
[EnumMember]
MyOption1 = 1,
[EnumMember]
MyOption2 = 2,
[EnumMember]
MyOption3 = 4,
[EnumMember]
MyOption4 = 8
}
[ServiceContract]
public interface MyServiceContract
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "resource?options={options}")]
void MyOperation(OptionsEnum options);
}
The resource can then be requested via URLs like this:
GET /resource?options=None
GET /resource?options=MyOption1
GET /resource?options=MyOption1,MyOption3
All of this works really nicely as long the URL actually contains a value for the options parameter. However, if I request the resource without specifying a value in the URL, like this:
GET /resource
I get an exception with the message Value cannot be null.\r\nParameter name: value
and the following stack trace:
at System.Enum.TryParseEnum(Type enumType, String value, Boolean ignoreCase, EnumResult& parseResult)
at System.Enum.Parse(Type enumType, String value, Boolean ignoreCase)
at System.ServiceModel.Dispatcher.QueryStringConverter.ConvertStringToValue(String parameter, Type parameterType)
at System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter.DeserializeRequest(Message message, Object[] parameters)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
Obviously, this is because QueryStringConverter passes null into Enum.Parse(...) in this case. As a result the implementation of MyServiceContract will not be executed.
Of course I could just switch to string for the type of the options parameter and do all the parsing stuff myself within the service implementation, but that's not what I want, really.
Does anyone know a clean solution to have OptionsEnum.None passed into the service implementation if the URL does not contain a value (just like 0 is passed for omitted parameters of type int)?
I already tried using a custom TypeConverter implementation, but even that does not seem to work. Looking at the implementation of QueryStringConverter it seems like it will always try to convert enum types by itself.
Ok, I have found a solution that is reusable and does not involve switching to string for the type of the flags parameter. I was hoping for something simpler, though. Anyway, in case it helps someone else, here it is.
The approach is relatively simple:
Wrap the enum in a reference type.
Use a TypeConverter to customize the process of converting the value from string to our flags enum as implemented by WCF's QueryStringConverter.
/// <summary>
/// Wraps a flags enum value.
/// </summary>
/// <remarks>
/// This class is meant to be used in conjunction with
/// <see cref="FlagsConverter{TFlags,TEnum}"/> and simply boxes an enum.
/// This is necessary in order to customize WCF's default behavior for enum
/// types (as implemented by <see cref="QueryStringConverter"/>) by using a
/// <see cref="TypeConverter"/>.
/// </remarks>
/// <devdoc>
/// We prefer this over using an 1-Tuple (<see cref="Tuple{T1} "/>) as it
/// allows us to add constraints on the type parameter. Also, the value
/// wrapped by a <see cref="Tuple{T1} "/> is read-only, which we don't want
/// here, as there is no reason to prevent [OperationContract] methods from
/// writing the wrapped <see cref="Value"/>.
/// </devdoc>
/// <typeparam name="TEnum">
/// The enum type. Must be attributed with <see cref="FlagsAttribute"/>.
/// </typeparam>
public abstract class Flags<TEnum>
where TEnum : struct, IConvertible
{
// Use a static c'tor to add run-time checks on the type parameter that
// cannot be checked at compile-time via constraints.
static Flags()
{
if (!typeof(TEnum).IsEnum)
{
throw new InvalidOperationException("T is not an enum");
}
if (!typeof(TEnum).IsDefined(typeof(FlagsAttribute), false))
{
throw new InvalidOperationException("T is not a flags enum");
}
}
/// <summary>
/// The enum value.
/// </summary>
public TEnum Value
{
get;
set;
}
}
/// <summary>
/// A <see cref="TypeConverter"/> implementation that converts from
/// <c>string</c> to <see cref="Flags{TEnum}"/>.
/// </summary>
/// <remarks>
/// <para>
/// Meant to be used in conjunction with <see cref="Flags{TEnum}"/>.
/// The purpose of this <see cref="TypeConverter"/> implementation is to
/// convert both <c>null</c> and the empty string to <c>default(TEnum)</c>
/// instead of throwing an exception. This way, a flags enum (wrapped in an
/// instance of <see cref="Flags{TEnum}"/>) can be used as the type of an
/// optional parameter in a RESTful WCF <c>OperationContract</c> method.
/// </para>
/// <para>
/// If the string value (as provided by <see cref="QueryStringConverter"/>)
/// is <c>null</c> or empty or can be successfully parsed into an enum
/// value of type <typeparamref name="TEnum"/>, this implementation will
/// provide an instance of <typeparamref name="TFlags"/>. Thus, there is no
/// need to check a <typeparamref name="TFlags"/>-typed value for
/// <c>null</c> within the implementation of an <c>OperationContract</c>
/// method.
/// </para>
/// </remarks>
/// <typeparam name="TFlags">
/// A sub-class of <see cref="Flags{TEnum}"/>. Must have a default c'tor.
/// </typeparam>
/// <typeparam name="TEnum">
/// The enum type. Must be attributed with <see cref="FlagsAttribute"/>.
/// </typeparam>
public class FlagsConverter<TFlags, TEnum> : TypeConverter
where TFlags : Flags<TEnum>, new()
where TEnum : struct, IConvertible
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (string.IsNullOrEmpty((string)value))
{
// The following line is what Flags<> and FlagsConverter<,> is
// all about: Provide the enum's default value (meaning no flag
// is set) for null and the empty string instead of passing it
// into Enum.Parse() (which results in an ArgumentException).
return new TFlags { Value = default(TEnum) };
}
// Otherwise, just pass the value on the Enum.Parse(), i.e. don't
// add any further changes to WCF's default behavior as implemented
// by QueryStringConverter.
return new TFlags { Value = (TEnum)Enum.Parse(typeof(TEnum), (string)value, true) };
}
}
These two classes can now be used to solve the problem like this
(reusing the example from the original question):
[DataContract]
[Flags]
public enum OptionsEnum
{
[EnumMember]
None = 0,
[EnumMember]
MyOption1 = 1,
[EnumMember]
MyOption2 = 2,
[EnumMember]
MyOption3 = 4,
[EnumMember]
MyOption4 = 8
}
[DataContract]
[TypeConverter(typeof(FlagsConverter<MyOptionalFlags, OptionsEnum>))]
public class MyOptionalFlags
: Flags<OptionsEnum>
{
// We don't add anything here, as the whole purpose of this class is to
// wrap the OptionsEnum in a class that will be instantiated by the
// attributed FlagsConverter.
}
[ServiceContract]
public interface MyServiceContract
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "resource?options={options}")]
void MyOperation(MyOptionalFlags options);
}
public class MyServiceContractImpl
: MyServiceContract
{
public void MyOperation(MyOptionalFlags options)
{
if (options.Value == OptionsEnum.None)
{
// We will now get here for requests that do not specify a
// value for the options parameter in the URL. Note that just
// like for an enum value, we don't have to check if options is
// null here.
}
}
}
I'm using the fluent pattern for helping with unit testing, and result object building. The biggest pain point of the fluent builder pattern is having to define all these With____ methods for each and every property that I might want to set.
When it comes to an object with maybe 30 fields that I might want to set, I don't exactly want to write out 30 methods that all pretty much do the same thing. I'd rather just write out something dynamic that can handle all the similar logic for me.
For example (psuedo code)
for each property in this.properties
define method( property.name with
return type: this.class,
parameter types: [property.type]){
set property property.name, parameters[0]
return this
}
Here is what I have so far in c#
var properties = typeof(EngineModelBuilder).GetProperties();
foreach (var property in properties){
// how do I create a method here with the property?
// a property has .PropertyType and a .Name
// and the return type is always going to be 'this'
}
For reference, here is how a normal fluent builder method looks:
public EngineModelBuilder WithDescription(string description)
{
_description = description;
return this;
}
This is the class I came up with. It utilizes C#'s dynamic object to use the "method_missing" approach of run-time functionality.
Note that method_missing is from Ruby, and is commonly used for this kind of dynamic behavior.
DynamicBuilder.cs:
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace PGenCore
{
/// <summary>
/// TODO: test different field retreival techniques (using fields from T, and managing in a Dictionary)
/// - how would we define defaults?
///
///
/// Usage:
/// dynamic objectBuilder = new ObjectBuilder();
/// var object = objectBuilder
/// .WithObjectFieldName(appropriateValue)
/// .WithObjectField(appropriateValue2)
/// .Build();
///
/// </summary>
/// <typeparam name="T">Type to Build</typeparam>
public class DynamicBuilder<T> : DynamicObject
{
#region List of FieldInformation
protected FieldInfo[] FieldInfos;
public FieldInfo[] FieldInformation
{
get
{
// don't GetFields all the time
if (FieldInfos != null) return FieldInfos;
FieldInfos = this.GetType().GetFields(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance);
return FieldInfos;
}
}
#endregion
#region Utility
/// <summary>
/// converts FieldName to _fieldName
/// </summary>
/// <param name="publicName"></param>
/// <returns></returns>
public static string PublicNameToPrivate(string publicName)
{
var propertyLowerFirstLetterName = char.ToLower(
publicName[0]) + publicName.Substring(1);
var privateName = "_" + propertyLowerFirstLetterName;
return privateName;
}
#endregion
#region Method is Missing? Check for With{FieldName} Pattern
/// <summary>
/// Inherited form DynamicObject.
/// Ran before each method call.
///
///
/// Note: Currently only works for setting one value
/// at a time.
/// e.g.: instance.Object = value
/// </summary>
/// <param name="binder"></param>
/// <param name="args"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryInvokeMember(
InvokeMemberBinder binder,
object[] args,
out object result)
{
var firstArgument = args[0];
var methodName = binder.Name;
var propertyRootName = methodName;
// following the builder pattern,
// methods that participate in building T,
// return this so we can chain building methods.
result = this;
// for booleans, since the field / property should be named as
// a question, using "With" doesn't make sense.
// so, this logic is only needed when we are not setting a
// boolean field.
if (!(firstArgument is bool))
{
// if we are not setting a bool, and we aren't starting
// with "With", this method is not part of the
// fluent builder pattern.
if (!methodName.Contains("With")) return false;
propertyRootName = methodName.Replace("With", "");
}
// convert to _privateFieldName
// TODO: think about dynamicly having fields in a Dictionary,
// rather than having to specify them
var builderFieldName = PublicNameToPrivate(propertyRootName);
// find matching field name, given the method name
var fieldInfo = FieldInformation
.FirstOrDefault(
field => field.Name == builderFieldName
);
// if the field was not found, abort
if (fieldInfo == null) return false;
// set the field to the value in args
fieldInfo.SetValue(this, firstArgument);
return true;
}
#endregion
/// <summary>
/// Returns the built object
/// </summary>
/// <returns></returns>
public virtual T Build()
{
throw new NotImplementedException();
}
/// <summary>
/// for any complex associations
/// - building lists of items
/// - building anything else that isn't just an easy vavlue.
/// </summary>
public virtual void SetRelationshipDefaults()
{
throw new NotImplementedException();
}
}
}
Tests of usage can be seen here: https://github.com/NullVoxPopuli/Procedural-Builder/blob/master/ProceduralBuilder.Test/DynamicBuilderTest.cs
I have the following code w/comments: (It does compile)
/// <summary>
/// return a passing result of a particular type
/// </summary>
/// <typeparam name="T">Type of the value to be returned</typeparam>
/// <param name="value">the value to be returned</param>
/// <returns>a passing result</returns>
public static Result<T> Pass(T value)
{
return new Result<T>()
{
Passed = true,
Value = value
};
}
I get the following warning with it:
Warning 1 SA1620 : CSharp.Documentation :
The typeparam tags in the documentation header must
match the generic types for the method.
I did look at the help page for this error, which gives this explanation:
To fix a violation of this rule, add and fill-in one tag
for each generic type parameter on the element, and make sure that
the tags appear in the same order as the element’s type
parameters.
And it has provided sample code:
/// <summary>
/// A sample generic class.
/// </summary>
/// <typeparam name="S">The first generic type parameter.</typeparam>
/// <typeparam name="T">The second generic type parameter.</typeparam>
public class Class1<S, T>
{
}
I don't see anything about mine that breaks the standards it is showing, and I have tried various odd things, but I have no real idea of what I'm supposed to do here.
The only way that this can compile is if this method is inside a class that is generic in T. This method doesn't have any type parameters. If it was generic, there would be type parameters after the name of the method:
public static Result<T> Pass<T>(T value)
{
return new Result<T>()
{
Passed = true,
Value = value
};
}
But that's not the case with your method. So it must be:
class SomeClass<T>
{
public static Result<T> Pass(T value)
{
return new Result<T>()
{
Passed = true,
Value = value
};
}
}
And any documentation about the type parameter belongs up at the class level. E.g.:
/// <summary>
/// This is a result class
/// </summary>
/// <typeparam name="T">Type of the value to be returned</typeparam>
public class Result<T>
{
public bool Passed { get; set; }
public T Value { get; set; }
/// <summary>
/// return a passing result of a particular type
/// </summary>
/// <param name="value">the value to be returned</param>
/// <returns>a passing result</returns>
public static Result<T> Pass(T value)
{
return new Result<T>()
{
Passed = true,
Value = value
};
}
}
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);
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);
}
}
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);
}