Last active
March 7, 2018 12:33
-
-
Save JohnLBevan/0c1676b2287255686402d680c399f87b to your computer and use it in GitHub Desktop.
Futureproof enum classes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| A while back I started using classes with static properties to represent enums | |
| More on that here: https://stackoverflow.com/a/15713789/361842 | |
| This is the beginning of a reusable base class to allow me to easily create | |
| these pseudo-enums without having to repeat myself each time. | |
| Not yet complete; once done I'll add another file to this gist to represent a couple of use cases... | |
| */ | |
| public abstract class AdvancedEnum | |
| { | |
| #region attributes | |
| static AdvancedEnumCollection collection = new AdvancedEnumCollection(); | |
| #endregion attributes | |
| #region Properties | |
| private readonly int id; | |
| public int Id { get { return id; } } | |
| private readonly string name; | |
| public string Name { get { return name; } } | |
| private readonly string displayName; | |
| public string DisplayName { get { return displayName;} } | |
| #endregion Properties | |
| #region Constructors | |
| protected AdvancedEnum(int id, string name) : this(id, name, name) { } | |
| protected AdvancedEnum(int id, string name, string displayName) | |
| { | |
| this.id = id; | |
| this.name = name; | |
| this.displayName = displayName; | |
| collection.Add(this); | |
| } | |
| #endregion Constructors | |
| #region Overridden Methods | |
| public override bool Equals(object obj) | |
| { | |
| return Equals(obj as AdvancedEnum); | |
| } | |
| public bool Equals(AdvancedEnum obj) | |
| { | |
| return obj != null | |
| && id.Equals(obj.id) | |
| && name.Equals(obj.name) | |
| && displayName.Equals(obj.DisplayName); | |
| } | |
| #endregion Overridden Methods | |
| } | |
| public class AdvancedEnumCollection: ICollection<AdvancedEnum> | |
| { | |
| static object syncLock = new object(); | |
| IDictionary<int, AdvancedEnum> allValuesById = new Dictionary<int, AdvancedEnum>(); | |
| public int Count => allValuesById.Count(); | |
| public bool IsReadOnly => false; //we want to make it read only once fully populated; but for now just leave as false | |
| public void Add(AdvancedEnum item) | |
| { | |
| if (AssertIdIsUnique(item)) //if the item is not unique error straight away | |
| { | |
| lock (syncLock) //if it was unique lock the collection, then recheck in more detail that it's still unique / that other properties are also unique | |
| { | |
| AssertIdIsUnique(item); | |
| AssertNameIsUnique(item); | |
| AssertDisplayNameIsUnique(item); | |
| //if confirmed that all three properties are unique (i.e. don't already exist) then add the item to the collection | |
| allValuesById.Add(item.Id, item); | |
| } | |
| } | |
| } | |
| private bool AssertIdIsUnique(AdvancedEnum item) | |
| { | |
| if (allValuesById.ContainsKey(item.Id)) | |
| throw new DuplicateException<int>(item.Id, "Id"); | |
| return true; | |
| } | |
| private bool AssertNameIsUnique(AdvancedEnum item) | |
| { | |
| if (allValuesById.Any(x => x.Value.Name.Equals(item.Name, StringComparison.Ordinal))) | |
| throw new DuplicateException<string>(item.Name, "Name"); | |
| return true; | |
| } | |
| private bool AssertDisplayNameIsUnique(AdvancedEnum item) | |
| { | |
| if (allValuesById.Any(x => x.Value.DisplayName.Equals(item.DisplayName,StringComparison.Ordinal))) | |
| throw new DuplicateException<string>(item.Name, "DisplayName"); | |
| return true; | |
| } | |
| public void Clear() | |
| { | |
| //values don't change after population; there's no good reason to allow this method | |
| throw new InvalidOperationException(); | |
| } | |
| public bool Contains(AdvancedEnum item) | |
| { | |
| return allValuesById.ContainsKey(item.Id) | |
| && allValuesById[item.Id].Equals(item); | |
| } | |
| public void CopyTo(AdvancedEnum[] array, int arrayIndex) | |
| { | |
| allValuesById.Values.CopyTo(array, arrayIndex); | |
| } | |
| public IEnumerator<AdvancedEnum> GetEnumerator() | |
| { | |
| return allValuesById.Values.GetEnumerator(); | |
| } | |
| public bool Remove(AdvancedEnum item) | |
| { | |
| //values don't change after population; there's no good reason to allow this method | |
| throw new InvalidOperationException(); | |
| } | |
| IEnumerator IEnumerable.GetEnumerator() | |
| { | |
| return ((IEnumerable)allValuesById.Values).GetEnumerator(); | |
| } | |
| } | |
| public class DuplicateException<T> : ArgumentException | |
| { | |
| //for simplicity we'll hardcode the messages here; in reality these should be populated from language specific resource files. | |
| const string KeyIsNotUnique = "Key value '{0}' is not unique within collection."; | |
| const string PropertyValueIsNotUnique = "Value '{0}' is not unique within collection for property '{1}'."; | |
| public DuplicateException() : base() { } | |
| public DuplicateException(string message) : base(message) { } | |
| public DuplicateException(string message, Exception innerException) : base(message, innerException) { } | |
| public DuplicateException(T key) : this(string.Format(KeyIsNotUnique, key)) { } | |
| public DuplicateException(T key, string propertyName) : this(string.Format(PropertyValueIsNotUnique, key, propertyName)) { } | |
| public DuplicateException(T key, Exception innerException) : this(string.Format(KeyIsNotUnique, key), innerException) { } | |
| public DuplicateException(T key, string propertyName, Exception innerException) : this(string.Format(PropertyValueIsNotUnique, key, propertyName), innerException) { } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment