I want to define a flag enum like the following:
[Flags]
[Serializable]
public enum Numbers
{
[XmlEnum(Name = "One")]
One = 0x1,
[XmlEnum(Name = "Two")]
Two = 0x2,
[XmlEnum(Name = "Three")]
Three = 0x4,
[XmlEnum(Name = "OddNumbers")]
OddNumbers = One | Three,
[XmlEnum(Name = "EvenNumbers")]
EvenNumbers = Two,
[XmlEnum(Name = "AllNumbers")]
AllNumbers = One | Two | Three
}
Suppose I create an object which has a Numbers property and I set that property to EvenNumbers. Today, only Two is included in property, but I want to specify EvenNumbers so that if I add Four in the future it will be included in the property as well.
However, if I serialize this object using XmlSerializer.Serialize, the XML will say Two because today this is the same underlying value.
How can I force the serializer to serialize this property as EvenNumbers?
Do something like that: Mark the Number property as XmlIgnore and create another property type of string for return the string value of enum.
[Serializable]
public class NumberManager
{
[XmlIgnore]
public Numbers Numbers { get; set; }
[XmlAttribute(AttributeName="Numbers")]
public string NumbersString
{
get { return Numbers.ToString(); }
set { Numbers = (Numbers) Enum.Parse(typeof (Numbers), value); }
}
}
Then serialize the NumberManager.
I hope it helps.
Related
I have an enum that holds feature names such as
public enum FeatureNames{
[Description("Amazing Feature")]
AmazingFeature,
[Description("Amazing New Feature")]
AmazingNewFeature
}
I have a domain entity that has properties representing each of the feature which indicates if the feature is turned on or off
public class CompanyAccount{
public bool IsAmazingFeatureEnabled{get;set;}
public bool IsAmazingNewFeatureEnabled{get;set;}
}
This is how the existing application was written and hence I don't want to make drastic changes to DB at this point. So now I am trying to write a genric method that will take in a string or enum value indicating feature and check if the property matching the same feature is enabled or not(true or false).
public bool IsEnabled(FeatureNames featureName){
if(featureName is FeatureNames.AmazingFeature){
return companyAccount.IsAmazingFeatureEnabled;
}
else if(featureName is FeatureNames.AmazingNewFeature){
return companyAccount.IsAmazingNewFeatureEnabled;
}
Is there a better way to handle this? Can we avoid the if condition?
You could use a switch expression (C# 8.0)
public bool IsEnabled(FeatureNames featureName) =>
featureName switch {
FeatureNames.AmazingFeature => companyAccount.IsAmazingFeatureEnabled,
FeatureNames.AmazingNewFeature => companyAccount.IsAmazingNewFeatureEnabled,
_ => false
};
or use a throw expression in the default case.
_ => throw new NotImplementedException($"{featureName} not implemented")
Another approach would be to convert the enum into a flags enum and to add a property returning the state as flag set.
[Flags]
public enum FeatureNames {
None = 0,
AmazingFeature = 1,
AmazingNewFeature = 2 // use powers of 2 for all values
}
public class CompanyAccount{
public bool IsAmazingFeatureEnabled { get; set; }
public bool IsAmazingNewFeatureEnabled { get; set; }
public FeatureNames State {
get {
var flags = FeatureNames.None;
if (IsAmazingFeatureEnabled) flags = flags | FeatureNames.AmazingFeature;
if (IsAmazingNewFeatureEnabled) flags = flags | FeatureNames.AmazingNewFeature;
return flags;
}
}
}
Then you can test the state with
if (companyAccount.State.HasFlag(FeatureNames.AmazingFeature)) ...
Using the flag enums, we could turn things around and store the state as flags and then put the logic in the Boolean properties (showing just one property here):
private FeatureNames _state;
public bool IsAmazingFeatureEnabled
{
get => _state.HasFlag(FeatureNames.AmazingFeature);
set => _state = value
? _state | FeatureNames.AmazingFeature
: _state & ~FeatureNames.AmazingFeature;
}
In C# or VB.NET, under WinForms, I have a property that returns an array of a enum. See an example:
public enum TestEnum: int {
Name1 = 0,
Name2 = 1,
Name3 = 2
} // Note that the enum does not apply for the [Flags] attribute.
public TestEnum[] TestProperty {get; set;} =
new[] {TestEnum.Name1, TestEnum.Name2, TestEnum.Name3};
By default, a PropertyGrid will show the values as int[], like: {0, 1, 2} instead of the enumeration value names, like: {"Name1", "Name2", "Name2"}, which is the visual representation that I would like to acchieve...
So, I would like to design a TypeConverter that could be able to return a string array with the value names, and apply it like this:
[TypeConverter(typeof(EnumArrayToStringArrayTypeConverter))]
public TestEnum[] TestProperty {get; set;} =
new[] {TestEnum.Name1, TestEnum.Name2, TestEnum.Name3};
In other words, If my property is represented like this in a PropertyGrid:
I would like to have this:
The biggest problem I'm facing is trying to retrieve the type of the enum from the custom type-converter class, to be able get the value names of that enum. I only can get the primitive data type of the array (like: int[], uint16[], etc)...
public class EnumArrayToStringArrayTypeConverter : TypeConverter {
// ...
public override object ConvertTo(ITypeDescriptorContext context,
CultureInfo culture,
object value,
Type destinationType) {
if (destinationType == null) {
throw new ArgumentNullException(nameof(destinationType));
}
try {
// This will return the array-type for the
// primitive data type of the declared enum,
// such as int[], uint16[], etc.
Type t = value.GetType();
// I'm stuck at this point.
// ...
} catch (Exception ex) {
}
return null;
}
// ...
}
Please take into account that I'm asking for a reusable solution that can work for any kind of enum. And, my enum in this example does not have the [Flags] attribute applied, but a solution should care about enums having it, so, If a enum item of the enum array is a enum that has various flags, those flags (the value names) should be concatenated for example using string.join().
The PropertyGrid does already show the names for the enum values. It can even handle [Flags] correctly. See the sample below using a form with a default PropertyGrid and a default button and nothing else.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[Flags]
public enum TestEnum : int
{
Name1 = 0,
Name2 = 1,
Name3 = 2
}
public class TestObject
{
public string Name { get; set; } = "Hello World";
public TestEnum[] TestProperty { get; set; } =
new[] { TestEnum.Name1, TestEnum.Name2 | TestEnum.Name3, TestEnum.Name3 };
}
private void button1_Click(object sender, EventArgs e)
{
TestObject o = new TestObject();
propertyGrid1.SelectedObject = o;
}
}
Please supply some example code that can reproduce that the enum names are not shown in the PropertyGrid. You must be doing something wrong in the first place.
As mentioned by #NineBerry in his answer, the PropertyGrid does already show the names for the enum values. However, I discovered that exists a strange circumstance on wich it will not do it...
Since my original source-code is written in VB.NET, I'll put VB.NET sample codes to reproduce the issue.
The thing is that I was getting a value from a instance of a WMI class (specifically: Win32_DiskDrive.Capabilities), which returns an object that needs to be casted to a uint16 array. Then, I was casting that resulting uint16 array to my type of enum. To simplify things, I will not show WMI code, but an object that represents what I was getting from WMI...
Dim wmiValue As Object = {1US, 2US, 3US}
Dim castedValue As UShort() = DirectCast(wmiValue, UShort())
TestProperty = DirectCast(castedValue, TestEnum())
So, when doing that type-casting, and thanks to #NineBerry answer, I discovered that for some reason the default type converter of TestProperty goes wrong and the PropertyGrid shows uint16 values instead of the enum value-names.
( Note that using DirectCast() or CType() in VB.NET, it didn't changed the PropertyGrid behavior. )
To fix the error, I ended using Array.ConvertAll(), and then the PropertyGrid properly shows the value-names...
Dim wmiValue As Object = {1US, 2US, 3US}
Dim castedValue As UShort() = DirectCast(wmiValue, UShort())
TestProperty = Array.ConvertAll(castedValue,
Function(value As UShort)
Return DirectCast(value, TestEnum)
End Function)
I have currently next field in my serilizable class:
[DataMember]
[XmlElement(DataType = "string")]
public string Type {get;set;}
And in XML it is serilized like:
<Type>Type1</Type>
<Type>Type2</Type>
and so on...
I want it to be enum
public enum MyType
{
Type1,
Type2,
...
}
But how can I serialize it same way as simple string? Is it even possible?
You can do it like this
public enum EmployeeStatus
{
[XmlEnum(Name = "Single")]
One,
[XmlEnum(Name = "Double")]
Two,
[XmlEnum(Name = "Triple")]
Three
}
more about visit msdn.
You can decorate your enum element with XmlEnum:
public enum MyType
{
[XmlEnum(Name="Type1")]
Type1,
[XmlEnum(Name="Type2")]
Type2,
...
}
Give proper Name to make it appear in the XML the way you want.
I'm using ServiceStack v 3.9.71 and the ServiceStack.Text.EnumMemberSerializer assembly to serialize enums into readable text.
This works great, my enum values are serialized into the name I've specified using the EnumMemberAttribute.
The problem, though, is Swagger does not use my names. My guess is it just calls the .ToString() method on the enum values rather than the EnumMemberAttribute value.
Here is the order in which I setup the serialization. (In AppHost):
new EnumSerializerConfigurator()
.WithEnumTypes(new Type[] { typeof(MyEnum) })
.Configure();
Plugins.Add(new SwaggerFeature());
It doesn't seem to matter if the enum serializer is set before or after the swagger feature is added.
You are correct that the Swagger code does not use ServiceStack.Text.EnumMemberSerializer when parsing enum values. It only uses an Enum.GetValues here. Note that this is still the same in v4.
You can submit a pull request to make this change, but I'm not familiar with EnumMemberSerialzer and how it allows for retrieving the list of enum options. You may instead be able to use a string property decorated with ApiAllowableValues to achieve the affect.
Here is the solution I came up with (with the help of bpruitt-goddard, thanks mate):
The enum:
public enum MyEnum
{
[EnumMember(Value = "Value One")]
Value1 = 1,
[EnumMember(Value = "Value Two")]
Value2 = 2,
[EnumMember(Value = "Value Three")]
Value3 = 3
}
The client object:
public class MyClientObject
{
[Description("The name")]
public string Name {get;set;}
[Description("The client object type")]
[ApiAllowableValues("MyEnum", "Value One", "Value Two", "Value Three")]
public MyEnum MyEnum { get; set; }
}
Inside the AppHost:
new EnumSerializerConfigurator()
.WithEnumTypes(new Type[] { typeof(MyEnum) })
.Configure();
Now the enum is serialized properly and the Swagger documentation is correct. The only issue with this is having the names in two different places. Perhaps there is a way to check the names match via a unit test.
I came up with, in my opinion, a better solution. I wrote a class that extends the ApiAllowableValuesAttribute:
public class ApiAllowableValues2Attribute : ApiAllowableValuesAttribute
{
public ApiAllowableValues2Attribute(string name, Type enumType)
: base(name)
{
List<string> values = new List<string>();
var enumTypeValues = Enum.GetValues(enumType);
// loop through each enum value
foreach (var etValue in enumTypeValues)
{
// get the member in order to get the enumMemberAttribute
var member = enumType.GetMember(
Enum.GetName(enumType, etValue)).First();
// get the enumMember attribute
var enumMemberAttr = member.GetCustomAttributes(
typeof(System.Runtime.Serialization.EnumMemberAttribute), true).First();
// get the enumMember attribute value
var enumMemberValue = ((System.Runtime.Serialization.EnumMemberAttribute)enumMemberAttr).Value;
values.Add(enumMemberValue);
}
Values = values.ToArray();
}
}
The client object:
public class MyClientObject
{
[Description("The name")]
public string Name {get;set;}
[Description("The client object type")]
[ApiAllowableValues2("MyEnum", typeof(MyEnum))]
public MyEnum MyEnum { get; set; }
}
Now you don't have to specify the names again or worry about a name change breaking your Swagger documentation.
I have 2 enums in 2 different objects. I want to set the enum in object #1 equal to the enum in object #2.
Here are my objects:
namespace MVC1 {
public enum MyEnum {
firstName,
lastName
}
public class Obj1{
public MyEnum enum1;
}
}
namespace MVC2 {
public enum MyEnum {
firstName,
lastName
}
public class Obj2{
public MyEnum enum1;
}
}
I want to do this, but this wont compile:
MVC1.Obj1 obj1 = new MVC1.Obj1();
MVC2.Obj2 obj2 = new MVC2.Obj2();
obj1.enum1 = obj2.enum1; //I know this won't work.
How do I set the enum in Obj1 equal to the enum in Obj2? Thanks
Assuming that you keep them the same, you can cast to/from int:
obj1.enum1 = (MVC1.MyEnum)((int)obj2.enum1);
Enums have an underlying integer type, which is int (System.Int32) by default, but you can explicitly specify it too, by using "enum MyEnum : type".
Because you're working in two different namespaces, the Enum types are essentially different, but because their underlying type is the same, you can just cast them:
obj1.enum1 = (MVC1.MyEnum) obj2.enum1;
A note: In C# you have to use parentheses for function calls, even when there aren't any parameters. You should add them to the constructor calls.
Best way to do it is check if it's in range using Enum.IsDefined:
int one = (int)obj2.enum1;
if (Enum.IsDefined(typeof(MVC1.MyEnum), one )) {
obj1.enum1 = (MVC1.MyEnum)one;
}
obj1.enum1 = (MVC1.MyEnum) Enum.Parse(typeof(MVC1.MyEnum),
((int)obj2.enum1).ToString());
or
int one = (int)obj2.enum1;
obj1.enum1 = (MVC1.MyEnum)one;