Reputation: 2167
Following This post : Associating enums with strings in C#
I wanted to go further as it didn't quite fully met my needs for a Enum like Class that would act as string I ended-up with a solution that allows me to do the following:
string test1 = TestEnum.Analyze; //test1 == "ANALYZE"
string test1bis = (string)TestEnum.Analyze; //test1bis == "ANALYZE"
TestEnum test2 = "ANALYZE"; //test2 == {ANALYZE}
TestEnum test3 = "ANYTHING"; //test3 == null
As seen below in the unitTests all these work fine with this:
public class TestEnum : EnumType<TestEnum>
{
public static TestEnum Analyze { get { return new EnumType<TestEnum>("ANALYZE"); } }
public static TestEnum Test { get { return new EnumType<TestEnum>("TEST"); } }
public static implicit operator TestEnum(string s) => (EnumType<TestEnum>) s;
public static implicit operator string(TestEnum e) => e.Value;
}
I can't decide if this solution is elegant or incredibly stupid, It seems to me probably unnecessary complex and I might be messing a much easier solution in any case it could help someone so I'm putting this here.
//for newtonsoft serialization
[JsonConverter(typeof(EnumTypeConverter))]
public class EnumType<T> where T : EnumType<T> , new()
{
public EnumType(string value= null)
{
Value = value;
}
//for servicestack serialization
static EnumType()
{
JsConfig<EnumType<T>>.DeSerializeFn = str =>
{
return (T)str ;
};
JsConfig<EnumType<T>>.SerializeFn = type =>
{
return type.Value;
};
JsConfig<T>.DeSerializeFn = str =>
{
return (T)str;
};
JsConfig<T>.SerializeFn = type =>
{
return type.Value;
};
}
protected string Value { get; set; }
public static T Parse(string s)
{
return (T)s;
}
public override string ToString()
{
return Value;
}
public static EnumType<T> ParseJson(string json)
{
return (T)json;
}
public static implicit operator EnumType<T>(string s)
{
if (All.Any(dt => dt.Value == s))
{
return new T { Value = s };
}
else
{
var ai = new Microsoft.ApplicationInsights.TelemetryClient(Connector.tconfiguration);
ai.TrackException(new Exception($"Value {s} is not acceptable value for {MethodBase.GetCurrentMethod().DeclaringType}, Acceptables values are {All.Select(item => item.Value).Aggregate((x, y) => $"{x},{y}")}"));
return null;
}
}
public static implicit operator string(EnumType<T> dt)
{
return dt?.Value;
}
public static implicit operator EnumType<T>(T dt)
{
if (dt == null) return null;
return new EnumType<T>(dt.Value);
}
public static implicit operator T(EnumType<T> dt)
{
if (dt == null) return null;
return new T { Value = dt.Value };
}
public static bool operator ==(EnumType<T> ct1, EnumType<T> ct2)
{
return (string)ct1 == (string)ct2;
}
public static bool operator !=(EnumType<T> ct1, EnumType<T> ct2)
{
return !(ct1 == ct2);
}
public override bool Equals(object obj)
{
try
{
if(obj.GetType() == typeof(string))
{
return Value == (string)obj;
}
return Value == obj as T;
}
catch(Exception ex)
{
return false;
}
}
public override int GetHashCode()
{
return (!string.IsNullOrWhiteSpace(Value) ? Value.GetHashCode() : 0);
}
public static IEnumerable<T> All
=> typeof(T).GetProperties()
.Where(p => p.PropertyType == typeof(T))
.Select(x => (T)x.GetValue(null, null));
//for serialisation
protected EnumType(SerializationInfo info,StreamingContext context)
{
Value = (string)info.GetValue("Value", typeof(string));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Value",Value);
}
}
Here are the unit tests:
[TestFixture]
public class UnitTestEnum
{
Connector cnx { get;set; }
private class Test
{
public TestEnum PropertyTest { get; set; }
public string PropertyString { get; set; }
}
[SetUp]
public void SetUp()
{
typeof(EnumType<>)
.Assembly
.GetTypes()
.Where(x => x.BaseType?.IsGenericType == true && x.BaseType.GetGenericTypeDefinition() == typeof(EnumType<>))
.Each(x =>
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(x.BaseType.TypeHandle)
);
cnx = new Connector();
}
[TearDown]
public void Clear()
{
cnx.Dispose();
}
[Test]
public void EqualsString()
{
Assert.AreEqual(TestEnum.Analyze, TestEnum.Analyze);
Assert.AreEqual(TestEnum.Analyze,"ANALYZE");
Assert.IsTrue("ANALYZE" == TestEnum.Analyze);
Assert.IsTrue("ANALYZE".Equals(TestEnum.Analyze));
}
[Test]
public void Casts()
{
string test1 = TestEnum.Analyze;
string test1bis = (string)TestEnum.Analyze;
TestEnum test2 = "ANALYZE";
TestEnum test3 = "NAWAK";
Assert.AreEqual("ANALYZE", test1);
Assert.AreEqual("ANALYZE", test1bis);
Assert.IsTrue(test2 == TestEnum.Analyze);
Assert.IsTrue(test2.Equals(TestEnum.Analyze));
Assert.AreEqual(test3, null);
}
[Test]
public void Deserializations()
{
new List<TestEnum>
{
(TestEnum)ServiceStack.Text.JsonSerializer.DeserializeFromString("\"ANALYZE\"", typeof(TestEnum)),
"\"ANALYZE\"".FromJson<TestEnum>(),
(TestEnum)Newtonsoft.Json.JsonConvert.DeserializeObject("\"ANALYZE\"", typeof(TestEnum)),
Newtonsoft.Json.JsonConvert.DeserializeObject<TestEnum>("\"ANALYZE\"")
}.Each(testEnum => Assert.AreEqual(testEnum, TestEnum.Analyze));
new List<Test>
{
"{\"PropertyTest\":\"ANALYZE\",\"PropertyString\":\"ANALYZE\"}".FromJson<Test>(),
(Test)ServiceStack.Text.JsonSerializer.DeserializeFromString("{\"PropertyTest\":\"ANALYZE\",\"PropertyString\":\"ANALYZE\"}", typeof(Test)),
Newtonsoft.Json.JsonConvert.DeserializeObject<Test>("{\"PropertyTest\":\"ANALYZE\",\"PropertyString\":\"ANALYZE\"}"),
(Test)Newtonsoft.Json.JsonConvert.DeserializeObject("{\"PropertyTest\":\"ANALYZE\",\"PropertyString\":\"ANALYZE\"}",typeof(Test))
}.Each(test =>
{
Assert.AreEqual(test.PropertyTest, TestEnum.Analyze);
Assert.AreEqual(test.PropertyString, "ANALYZE");
});
}
[Test]
public void Serialisations()
{
Assert.AreEqual("{\"PropertyTest\":\"ANALYZE\",\"PropertyString\":\"ANALYZE\"}", new Test { PropertyTest = TestEnum.Analyze, PropertyString = TestEnum.Analyze }.ToJson());
Assert.AreEqual("{\"PropertyTest\":\"ANALYZE\",\"PropertyString\":\"ANALYZE\"}", Newtonsoft.Json.JsonConvert.SerializeObject(new Test { PropertyTest = TestEnum.Analyze, PropertyString = TestEnum.Analyze }));
Assert.AreEqual("\"ANALYZE\"", TestEnum.Analyze.ToJson());
Assert.AreEqual("\"ANALYZE\"", Newtonsoft.Json.JsonConvert.SerializeObject(TestEnum.Analyze));
}
[Test]
public void TestEnums()
{
Assert.AreEqual(TestEnum.All.Count(), 2);
Assert.Contains(TestEnum.Analyze,TestEnum.All.ToList());
Assert.Contains(TestEnum.Test,TestEnum.All.ToList());
}
Upvotes: 1
Views: 506
Reputation: 143284
Elegant code is typically described as simple, clean, terse with clear intent and optimally performant, I'm not seeing any of these traits here which is especially convoluted as instead of using simple C# Enum's as intended they're wrapped in a generic base class with implicit casts and custom serialization handling for different JSON Serialization libraries (that's unlikely to work in other serialization libraries/formats) and with with all the additional artificial complexity added it's not even clear what the benefit or purpose of all the boilerplate is?
Code that's drowned in so much boilerplate increases the maintenance burden and inhibits refactoring as no one else is going to know what the desired intent or purpose of the code is meant to be & why simpler naive solutions weren't adopted instead.
If you just want to have a different Wire Serialization format to the Symbol name used in code (which IMO should have a good reason for being different), you can just use the [EnumMember]
Serialization attribute:
[DataContract]
public enum TestEnum
{
[EnumMember(Value = "ANALYZE")]
Analyze,
[EnumMember(Value = "TEST")]
Test,
}
Otherwise I'd dispense with using Enums and go back to using string constants, which are simpler, faster, memory efficient and works in all Serialization libraries without custom serialization hacks:
public static class MyConstants
{
public const string Analyze = "ANALYZE";
public const string Test = "TEST";
}
Upvotes: 4