I'd like to Reset to there default values all of the members of my class instance CommunicationErrorsDetails.
This class is part of a nested class MyNestedClassInstance.
This is what I'd like to do :
MyNestedClassInstance.CommunicationErrorsDetails.Reset()
This a sample of my nested class MyNestedClass which instance is MyNestedClassInstance :
public class MyNestedClass : ICloneable
{
...
/// <summary>
/// Communication errors count
/// </summary>
public class CommunicationErrorsDetailsType : ICloneable
{
public int RetryCount;
public int CRCErrorCount;
public int DataBytesNotExpectedCount;
public int TooMuchDataReceivedCount;
public int ResponseDataAddressNotEqualCount;
public int BytesReceivedInCommunicationStateStartCount;
public int BytesReceivedInCommunicationStateSendFrameCount;
public int BytesReceivedInCommunicationStateDataResponseReceivedCount;
public int ExceptionCount;
public int NakcReceivedCount;
public int AckTimeoutCount;
public int DataTimeoutCount;
public double DataTimeoutRate;
public bool HasCommunicationErrors
{
get => RetryCount > 0
|| CRCErrorCount > 0
|| DataBytesNotExpectedCount > 0
|| TooMuchDataReceivedCount > 0
|| ResponseDataAddressNotEqualCount > 0
|| BytesReceivedInCommunicationStateStartCount > 0
|| BytesReceivedInCommunicationStateSendFrameCount > 0
|| BytesReceivedInCommunicationStateDataResponseReceivedCount > 0
|| ExceptionCount > 0
|| NakcReceivedCount > 0
|| AckTimeoutCount > 0
|| DataTimeoutCount > 0;
}
public object Clone()
{
return MemberwiseClone();
}
internal void Reset()
{
// ... ?
}
}
public CommunicationErrorsDetailsType CommunicationErrorsDetails = new CommunicationErrorsDetailsType();
...
// Other nested classes
...
}
How can I achieve Reset() without having to recreate a new instance and without having to reset manually all members that can be of different types ?
All members are simple types (not classes).
Furthermore, I cannot change the structure of all classes of same type because we have several years of code structured like this.
Thank you for your help.
Regards
One option is to take advantage of the fact that structs in .NET can easily reset themselves by assigning this.
An example code sample is below (also available at https://dotnetfiddle.net/zhfjcg). The key bit is:
public void Reset()
{
this = new TheData();
}
This will reset / reinitialise the TheData object - so that all fields are reset back to their default.
The code sample also uses properties to expose the struct data in CommunicationErrorsDetailsType. That isn't strictly necessary, but is likely a good idea.
Additionally, this technique makes cloning much faster (vs MemberwiseClone) assuming the contents of the struct don't contain anything with fancy cloning requirements, since assigning the struct to a new variable will "automatically" clone it.
using System;
namespace WhatEver
{
public struct TheData
{
public int RetryCount;
public int CRCErrorCount;
public int DataBytesNotExpectedCount;
public int TooMuchDataReceivedCount;
public int ResponseDataAddressNotEqualCount;
public int BytesReceivedInCommunicationStateStartCount;
public int BytesReceivedInCommunicationStateSendFrameCount;
public int BytesReceivedInCommunicationStateDataResponseReceivedCount;
public int ExceptionCount;
public int NakcReceivedCount;
public int AckTimeoutCount;
public int DataTimeoutCount;
public double DataTimeoutRate;
public void Reset()
{
this = new TheData();
}
}
public class CommunicationErrorsDetailsType
{
private TheData data;
public int DataTimeoutCount
{
get
{
return data.DataTimeoutCount;
}
set
{
data.DataTimeoutCount = value;
}
}
public bool HasCommunicationErrors
{
get => data.RetryCount > 0
|| data.CRCErrorCount > 0
|| data.DataBytesNotExpectedCount > 0
|| data.TooMuchDataReceivedCount > 0
|| data.ResponseDataAddressNotEqualCount > 0
|| data.BytesReceivedInCommunicationStateStartCount > 0
|| data.BytesReceivedInCommunicationStateSendFrameCount > 0
|| data.BytesReceivedInCommunicationStateDataResponseReceivedCount > 0
|| data.ExceptionCount > 0
|| data.NakcReceivedCount > 0
|| data.AckTimeoutCount > 0
|| data.DataTimeoutCount > 0;
}
public object Clone()
{
return MemberwiseClone();
}
internal void Reset()
{
data.Reset();
}
}
public class ToRun
{
public static void Main()
{
var hereWeGo = new CommunicationErrorsDetailsType();
hereWeGo.DataTimeoutCount = 4;
Console.WriteLine(hereWeGo.DataTimeoutCount);
hereWeGo.Reset();
Console.WriteLine(hereWeGo.DataTimeoutCount);
}
}
}
Using Reflexion you can achieve what you want and with the help of
DefaultExpression Class
internal void Reset()
{
// Retrieves only the fields of this Class
var bindingFlags = BindingFlags.Instance
| BindingFlags.NonPublic
| BindingFlags.Public;
List<FieldInfo> members = this.GetType()
.GetFields(bindingFlags)
.Where(f => f.MemberType == MemberTypes.Field)
.Where(value => value != null)
.ToList();
// Set all fields to its default type value
for (int i = 0; i < members.Count(); i++)
{
// This expression represents the default value of a type
// (0 for integer, null for a string, etc.)
Expression defaultExpr = Expression.Default(typeof(byte));
// The following statement first creates an expression tree,
// then compiles it, and then executes it.
var defaultValue = Expression.Lambda<Func<byte>>(defaultExpr).Compile()();
// Set the default value
members[i].SetValue(this, defaultValue);
}
}
Just to offer another idea. Use an indirection.
class Instance
{
public Holder<int> IntValue1;
public Holder<int> IntValue2;
public Holder<double> DoubleValue1;
public void Reset()
{
Holder.Reset();
}
internal class Holder
{
public static event EventHandler OnReset;
public static void Reset()
{
OnReset?.Invoke(null, null);
}
}
public class Holder<T>
{
private Holder()
{
Holder.OnReset += (_, __) => Value = default(T);
}
public T Value { get; set; }
public static implicit operator T(Holder<T> holder)
{
return holder.Value;
}
public static implicit operator Holder<T>(T value)
{
return new Holder<T>() { Value = value };
}
}
}
Then use it like:
instance.IntValue1 = 123;
instance.IntValue2 = 234;
instance.DoubleValue1 = 0.5;
instance.Reset();
If you're OK with redesigning your public fields to properties (which is a best practice anyways) you can use an internal dictionary to store the values:
private Dictionary<string, int> _values = new Dictionary<string, int>();
private int get_val(string key)
{
int output;
_values.TryGetValue(key, out output);
return output;
}
public int RetryCount {get {return get_val("RetryCount");} set {_values["RetryCount"] = value;}
public int CRCErrorCount {get {return get_val("CRCErrorCount");} set {_values["CRCErrorCount"] = value;}
public int DataBytesNotExpectedCount {get {return get_val("CRCErDataBytesNotExpectedCount rorCount");} set {_values["DataBytesNotExpectedCount "] = value;}
...
Then "resetting" an instance is just a one-liner
internal void Reset() { _values.Clear(); }
Note: my case is for byte[] but I believe a good answer would work for any type.
Visual Studio's auto-generated implementation of Equals uses EqualityComparer<T>.Default.Equals(T x, T y) for reference types. I have a lot of classes with byte arrays that needs to be included in Equals so I'd like to keep Visual studio's code if possible but Default returns a ObjectEqualityComparer for byte arrays. I've written a simple byte array comparer but I'm not sure how to proceed to have it used instead of ObjectEqualityComparer.
public class Foo
{
public int Id {get;set;}
public byte[] Data {get;set;}
public override bool Equals(object obj)
{
var foo = obj as Foo;
return foo != null &&
Id == foo.Id &&
EqualityComparer<byte[]>.Default.Equals(Data, foo.Data);
}
}
static void Main
{
Foo f1 = new Foo { Id = 1, Data = new byte[1] { 0xFF } };
Foo f2 = new Foo { Id = 1, Data = new byte[1] { 0xFF } };
bool result = f1.Equals(f2); // false
}
public class ByteArrayComparer
{
public bool Equals(byte[] x, byte[] y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(byte[] obj)
{
return obj.GetHashCode();
// as Servy said, this is wrong but it's not the point of the question,
// assume some working implementation
}
}
Should ByteArrayComparer implement IEqualityComparer, inherit from EqualityComparer and override the methods, or something else?
Create and use an instance of your custom comparer instead of using EqualityComparer<byte[]>.Default in your class:
public class Foo
{
public int Id { get; set; }
public byte[] Data { get; set; }
private readonly ByteArrayComparer _comparer = new ByteArrayComparer();
public override bool Equals(object obj)
{
var foo = obj as Foo;
return foo != null &&
Id == foo.Id &&
_comparer.Equals(Data, foo.Data);
}
}
You may also want to implement IEqualityComparer<T> and GetHashCode() in your ByteArrayComparer class. EqualityComparer<T>.Default returns an instance of a class that implements this interface, but I assume you don't want to use this one as you have implemented your own custom comparer.
How to use the IEqualityComparer
Assume the following code:
[ContractClass(typeof(ICC4Contract))]
public interface ICC4
{
bool IsFooSet { get; }
string Foo { get; }
}
public class CC4 : ICC4
{
private string _foo;
public bool IsFooSet { get { return Foo != null; } }
public string Foo { get { return _foo; } }
}
[ContractClassFor(typeof(ICC4))]
public abstract class ICC4Contract : ICC4
{
public bool IsFooSet
{
get
{
Contract.Ensures((Contract.Result<bool>() && Foo != null)
|| !Contract.Result<bool>());
return false;
}
}
public string Foo
{
get
{
Contract.Ensures((Contract.Result<string>() != null && IsFooSet)
|| !IsFooSet);
return null;
}
}
}
The contracts try to say:
IsFooSet will return true if Foo is not null.
Foo doesn't return null if IsFooSet returns true.
This almost works.
However, I get an "ensures unproven" on return _foo;, because the checker doesn't realize that Foo will always equal _foo.
Changing Foo to an automatic property with a private setter removes that warning, but I don't want to do that (I don't like automatic properties with private setters).
What do I have to change in the above code to make the warning go away while preserving the _foo field?
The following doesn't work:
Changing IsFooSet to use _foo instead of Foo. It will result in an additional "ensures unproven" on IsFooSet.
Adding an invariant Foo == _foo. This will result in an "invariant unproven" on the implicit, default constructor. Furthermore, on a real code-base the processing time of the static checker will be magnitudes higher.
Adding Contract.Ensures(Contract.Result<string>() == _foo); to the getter of Foo as per this answer doesn't change anything.
You can use short-circuiting to simplify the condition, and that works for some reason:
[ContractClassFor(typeof(ICC4))]
public abstract class ICC4Contract : ICC4
{
public bool IsFooSet
{
get
{
Contract.Ensures(!Contract.Result<bool>() || Foo != null);
return false;
}
}
public string Foo
{
get
{
Contract.Ensures(!IsFooSet || Contract.Result<string>() != null);
return null;
}
}
}
I'm looking at how build the best HashCode for a class and I see some algorithms. I saw this one : Hash Code implementation, seems to be that .NET classes HashCode methods are similar (see by reflecting the code).
So question is, why don't create the above static class in order to build a HashCode automatically, just by passing fields we consider as a "key".
// Old version, see edit
public static class HashCodeBuilder
{
public static int Hash(params object[] keys)
{
if (object.ReferenceEquals(keys, null))
{
return 0;
}
int num = 42;
checked
{
for (int i = 0, length = keys.Length; i < length; i++)
{
num += 37;
if (object.ReferenceEquals(keys[i], null))
{ }
else if (keys[i].GetType().IsArray)
{
foreach (var item in (IEnumerable)keys[i])
{
num += Hash(item);
}
}
else
{
num += keys[i].GetHashCode();
}
}
}
return num;
}
}
And use it as like this :
// Old version, see edit
public sealed class A : IEquatable<A>
{
public A()
{ }
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Value { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as A);
}
public bool Equals(A other)
{
if(object.ReferenceEquals(other, null))
? false
: Key1 == other.Key1 && Key2 == other.Key2;
}
public override int GetHashCode()
{
return HashCodeBuilder.Hash(Key1, Key2);
}
}
Will be much simpler that always is own method, no? I'm missing something?
EDIT
According all remarks, I got the following code :
public static class HashCodeBuilder
{
public static int Hash(params object[] args)
{
if (args == null)
{
return 0;
}
int num = 42;
unchecked
{
foreach(var item in args)
{
if (ReferenceEquals(item, null))
{ }
else if (item.GetType().IsArray)
{
foreach (var subItem in (IEnumerable)item)
{
num = num * 37 + Hash(subItem);
}
}
else
{
num = num * 37 + item.GetHashCode();
}
}
}
return num;
}
}
public sealed class A : IEquatable<A>
{
public A()
{ }
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Value { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as A);
}
public bool Equals(A other)
{
if(ReferenceEquals(other, null))
{
return false;
}
else if(ReferenceEquals(this, other))
{
return true;
}
return Key1 == other.Key1
&& Key2 == other.Key2;
}
public override int GetHashCode()
{
return HashCodeBuilder.Hash(Key1, Key2);
}
}
Your Equals method is broken - it's assuming that two objects with the same hash code are necessarily equal. That's simply not the case.
Your hash code method looked okay at a quick glance, but could actually do some with some work - see below. It means boxing any value type values and creating an array any time you call it, but other than that it's okay (as SLaks pointed out, there are some issues around the collection handling). You might want to consider writing some generic overloads which would avoid those performance penalties for common cases (1, 2, 3 or 4 arguments, perhaps). You might also want to use a foreach loop instead of a plain for loop, just to be idiomatic.
You could do the same sort of thing for equality, but it would be slightly harder and messier.
EDIT: For the hash code itself, you're only ever adding values. I suspect you were trying to do this sort of thing:
int hash = 17;
hash = hash * 31 + firstValue.GetHashCode();
hash = hash * 31 + secondValue.GetHashCode();
hash = hash * 31 + thirdValue.GetHashCode();
return hash;
But that multiplies the hash by 31, it doesn't add 31. Currently your hash code will always return the same for the same values, whether or not they're in the same order, which isn't ideal.
EDIT: It seems there's some confusion over what hash codes are used for. I suggest that anyone who isn't sure reads the documentation for Object.GetHashCode and then Eric Lippert's blog post about hashing and equality.
This is what I'm using:
public static class ObjectExtensions
{
/// <summary>
/// Simplifies correctly calculating hash codes based upon
/// Jon Skeet's answer here
/// http://stackoverflow.com/a/263416
/// </summary>
/// <param name="obj"></param>
/// <param name="memberThunks">Thunks that return all the members upon which
/// the hash code should depend.</param>
/// <returns></returns>
public static int CalculateHashCode(this object obj, params Func<object>[] memberThunks)
{
// Overflow is okay; just wrap around
unchecked
{
int hash = 5;
foreach (var member in memberThunks)
hash = hash * 29 + member().GetHashCode();
return hash;
}
}
}
Example usage:
public class Exhibit
{
public virtual Document Document { get; set; }
public virtual ExhibitType ExhibitType { get; set; }
#region System.Object
public override bool Equals(object obj)
{
return Equals(obj as Exhibit);
}
public bool Equals(Exhibit other)
{
return other != null &&
Document.Equals(other.Document) &&
ExhibitType.Equals(other.ExhibitType);
}
public override int GetHashCode()
{
return this.CalculateHashCode(
() => Document,
() => ExhibitType);
}
#endregion
}
Instead of calling keys[i].GetType().IsArray, you should try to cast it to IEnumerable (using the as keyword).
You can fix the Equals method without repeating the field list by registering a static list of fields, like I do here using a collection of delegates.
This also avoids the array allocation per-call.
Note, however, that my code doesn't handle collection properties.
I have an object that needs to be serialized to an EDI format. For this example we'll say it's a car. A car might not be the best example b/c options change over time, but for the real object the Enums will never change.
I have many Enums like the following with custom attributes applied.
public enum RoofStyle
{
[DisplayText("Glass Top")]
[StringValue("GTR")]
Glass,
[DisplayText("Convertible Soft Top")]
[StringValue("CST")]
ConvertibleSoft,
[DisplayText("Hard Top")]
[StringValue("HT ")]
HardTop,
[DisplayText("Targa Top")]
[StringValue("TT ")]
Targa,
}
The Attributes are accessed via Extension methods:
public static string GetStringValue(this Enum value)
{
// Get the type
Type type = value.GetType();
// Get fieldinfo for this type
FieldInfo fieldInfo = type.GetField(value.ToString());
// Get the stringvalue attributes
StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
typeof(StringValueAttribute), false) as StringValueAttribute[];
// Return the first if there was a match.
return attribs.Length > 0 ? attribs[0].StringValue : null;
}
public static string GetDisplayText(this Enum value)
{
// Get the type
Type type = value.GetType();
// Get fieldinfo for this type
FieldInfo fieldInfo = type.GetField(value.ToString());
// Get the DisplayText attributes
DisplayTextAttribute[] attribs = fieldInfo.GetCustomAttributes(
typeof(DisplayTextAttribute), false) as DisplayTextAttribute[];
// Return the first if there was a match.
return attribs.Length > 0 ? attribs[0].DisplayText : value.ToString();
}
There is a custom EDI serializer that serializes based on the StringValue attributes like so:
StringBuilder sb = new StringBuilder();
sb.Append(car.RoofStyle.GetStringValue());
sb.Append(car.TireSize.GetStringValue());
sb.Append(car.Model.GetStringValue());
...
There is another method that can get Enum Value from StringValue for Deserialization:
car.RoofStyle = Enums.GetCode<RoofStyle>(EDIString.Substring(4, 3))
Defined as:
public static class Enums
{
public static T GetCode<T>(string value)
{
foreach (object o in System.Enum.GetValues(typeof(T)))
{
if (((Enum)o).GetStringValue() == value.ToUpper())
return (T)o;
}
throw new ArgumentException("No code exists for type " + typeof(T).ToString() + " corresponding to value of " + value);
}
}
And Finally, for the UI, the GetDisplayText() is used to show the user friendly text.
What do you think? Overkill? Is there a better way? or Goldie Locks (just right)?
Just want to get feedback before I intergrate it into my personal framework permanently. Thanks.
The part you're using to serialize is fine. The deserialization part is awkwardly written. The main problem is that you're using ToUpper() to compare strings, which is easily broken (think globalization). Such comparisons should be done with string.Compare instead, or the string.Equals overload that takes a StringComparison.
The other thing is that performing these lookups again and again during deserialization is going to pretty slow. If you're serializing a lot of data, this could actually be quite noticeable. In that case, you'd want to build a map from the StringValue to the enum itself - throw it into a static Dictionary<string, RoofStyle> and use it as a lookup for the round-trip. In other words:
public static class Enums
{
private static Dictionary<string, RoofStyle> roofStyles =
new Dictionary<string, RoofStyle>()
{
{ "GTR", RoofStyle.Glass },
{ "CST", RoofStyle.ConvertibleSoft },
{ "HT ", RoofStyle.HardTop },
{ "TT ", RoofStyle.TargaTop }
}
public static RoofStyle GetRoofStyle(string code)
{
RoofStyle result;
if (roofStyles.TryGetValue(code, out result))
return result;
throw new ArgumentException(...);
}
}
It's not as "generic" but it's way more efficient. If you don't like the duplication of string values then extract the codes as constants in a separate class.
If you really need it to be totally generic and work for any enum, you can always lazy-load the dictionary of values (generate it using the extension methods you've written) the first time a conversion is done. It's very simple to do that:
static Dictionary<string, T> CreateEnumLookup<T>()
{
return Enum.GetValues(typeof(T)).ToDictionary(o => ((Enum)o).GetStringValue(),
o => (T)o);
}
P.S. Minor detail but you might want to consider using Attribute.GetCustomAttribute instead of MemberInfo.GetCustomAttributes if you only expect there to be one attribute. There's no reason for all the array fiddling when you only need one item.
Personally, I think you are abusing the language and trying to use enums in a way they were never intended. I would create a static class RoofStyle, and create a simple struct RoofType, and use an instance for each of your enum values.
Why don't you create a type with static members such as mikerobi said
Example...
public class RoofStyle
{
private RoofStyle() { }
public string Display { get; private set; }
public string Value { get; private set; }
public readonly static RoofStyle Glass = new RoofStyle
{
Display = "Glass Top", Value = "GTR",
};
public readonly static RoofStyle ConvertibleSoft = new RoofStyle
{
Display = "Convertible Soft Top", Value = "CST",
};
public readonly static RoofStyle HardTop = new RoofStyle
{
Display = "Hard Top", Value = "HT ",
};
public readonly static RoofStyle Targa = new RoofStyle
{
Display = "Targa Top", Value = "TT ",
};
}
BTW...
When compiled into IL an Enum is very similar to this class structure.
... Enum backing fields ...
.field public specialname rtspecialname int32 value__
.field public static literal valuetype A.ERoofStyle Glass = int32(0x00)
.field public static literal valuetype A.ERoofStyle ConvertibleSoft = int32(0x01)
.field public static literal valuetype A.ERoofStyle HardTop = int32(0x02)
.field public static literal valuetype A.ERoofStyle Targa = int32(0x03)
... Class backing fields ...
.field public static initonly class A.RoofStyle Glass
.field public static initonly class A.RoofStyle ConvertibleSoft
.field public static initonly class A.RoofStyle HardTop
.field public static initonly class A.RoofStyle Targa
Here is a base class I use for enumeration classes:
public abstract class Enumeration<T, TId> : IEquatable<T> where T : Enumeration<T, TId>
{
public static bool operator ==(Enumeration<T, TId> x, T y)
{
return Object.ReferenceEquals(x, y) || (!Object.ReferenceEquals(x, null) && x.Equals(y));
}
public static bool operator !=(Enumeration<T, TId> first, T second)
{
return !(first == second);
}
public static T FromId(TId id)
{
return AllValues.Where(value => value.Id.Equals(id)).FirstOrDefault();
}
public static readonly ReadOnlyCollection<T> AllValues = FindValues();
private static ReadOnlyCollection<T> FindValues()
{
var values =
(from staticField in typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public)
where staticField.FieldType == typeof(T)
select (T) staticField.GetValue(null))
.ToList()
.AsReadOnly();
var duplicateIds =
(from value in values
group value by value.Id into valuesById
where valuesById.Skip(1).Any()
select valuesById.Key)
.Take(1)
.ToList();
if(duplicateIds.Count > 0)
{
throw new DuplicateEnumerationIdException("Duplicate ID: " + duplicateIds.Single());
}
return values;
}
protected Enumeration(TId id, string name)
{
Contract.Requires(((object) id) != null);
Contract.Requires(!String.IsNullOrEmpty(name));
this.Id = id;
this.Name = name;
}
protected Enumeration()
{}
public override bool Equals(object obj)
{
return Equals(obj as T);
}
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
public override string ToString()
{
return this.Name;
}
#region IEquatable
public virtual bool Equals(T other)
{
return other != null && this.IdComparer.Equals(this.Id, other.Id);
}
#endregion
public virtual TId Id { get; private set; }
public virtual string Name { get; private set; }
protected virtual IEqualityComparer<TId> IdComparer
{
get { return EqualityComparer<TId>.Default; }
}
}
An implementation would look like:
public sealed class RoofStyle : Enumeration<RoofStyle, int>
{
public static readonly RoofStyle Glass = new RoofStyle(0, "Glass Top", "GTR");
public static readonly RoofStyle ConvertibleSoft = new RoofStyle(1, "Convertible Soft Top", "CST");
public static readonly RoofStyle HardTop = new RoofStyle(2, "Hard Top", "HT ");
public static readonly RoofStyle Targa = new RoofStyle(3, "Targa Top", "TT ");
public static RoofStyle FromStringValue(string stringValue)
{
return AllValues.FirstOrDefault(value => value.StringValue == stringValue);
}
private RoofStyle(int id, string name, string stringValue) : base(id, name)
{
StringValue = stringValue;
}
public string StringValue { get; private set; }
}
You would use it during serialization like this:
var builder = new StringBuilder();
builder.Append(car.RoofStyle.StringValue);
...
To deserialize:
car.RoofStyle = RoofStyle.FromStringValue(EDIString.Substring(4, 3));
I don't see a problem with it - actually, I do the same. By this, I achieve verbosity with the enum, and can define how the enum is to be translated when I use it to request data, eg. RequestTarget.Character will result in "char".
Can't say I've ever seen it done that way but the consumer code is relatively simple so I'd probably enjoy using it.
The only thing that sticks out for me is the potential for consumers having to deal with nulls - which might be able to be removed. If you have control over the attributes (which you do, from the sounds of it), then there should never be a case where GetDisplayText or GetStringValue return null so you can remove
return attribs.Length > 0 ? attribs[0].StringValue : null;
and replace it with
return attribs[0].StringValue;
in order to simplify the interface for consumer code.
IMHO, the design is solid, and will work.
However, reflection tends to be a litle slow, so if those methods are used in tight loops, it might slow down the whole application.
You could try caching the the return values into a Dictionary<RoofStyle, string> so they are only reflected once, and then fetched from cache.
Something like this:
private static Dictionary<Enum, string> stringValues
= new Dictionary<Enum,string>();
public static string GetStringValue(this Enum value)
{
if (!stringValues.ContainsKey(value))
{
Type type = value.GetType();
FieldInfo fieldInfo = type.GetField(value.ToString());
StringValueAttribute[] attribs = fieldInfo.GetCustomAttributes(
typeof(StringValueAttribute), false) as StringValueAttribute[];
stringValues.Add(value, attribs.Length > 0 ? attribs[0].StringValue : null);
}
return stringValues[value];
}
I know this question has already been answered, but while ago I posted the following code fragment on my personal blog, which demonstrates faking Java style enums using extension methods. You might find this method works for you, especially as it overcomes the overhead of accessing Attributes via reflection.
using System;
using System.Collections.Generic;
namespace ScratchPad
{
internal class Program
{
private static void Main(string[] args)
{
var p = new Program();
p.Run();
}
private void Run()
{
double earthWeight = 175;
double mass = earthWeight / Planet.Earth.SurfaceGravity();
foreach (Planet planet in Enum.GetValues(typeof(Planet))) {
Console.WriteLine("Your weight on {0} is {1}", planet, planet.SurfaceWeight(mass));
}
}
}
public enum Planet
{
Mercury,
Venus,
Earth,
Mars,
Jupiter,
Saturn,
Uranus,
Neptune
}
public static class PlanetExtensions
{
private static readonly Dictionary<Planet, PlanetData> planetMap = new Dictionary<Planet, PlanetData>
{
{Planet.Mercury, new PlanetData(3.303e+23, 2.4397e6)},
{Planet.Venus, new PlanetData(4.869e+24, 6.0518e6)},
{Planet.Earth, new PlanetData(5.976e+24, 6.37814e6)},
{Planet.Mars, new PlanetData(6.421e+23, 3.3972e6)},
{Planet.Jupiter, new PlanetData(1.9e+27, 7.1492e7)},
{Planet.Saturn, new PlanetData(5.688e+26, 6.0268e7)},
{Planet.Uranus, new PlanetData(8.686e+25, 2.5559e7)},
{Planet.Neptune, new PlanetData(1.024e+26, 2.4746e7)}
};
private const double G = 6.67300E-11;
public static double Mass(this Planet planet)
{
return GetPlanetData(planet).Mass;
}
public static double Radius(this Planet planet)
{
return GetPlanetData(planet).Radius;
}
public static double SurfaceGravity(this Planet planet)
{
PlanetData planetData = GetPlanetData(planet);
return G * planetData.Mass / (planetData.Radius * planetData.Radius);
}
public static double SurfaceWeight(this Planet planet, double mass)
{
return mass * SurfaceGravity(planet);
}
private static PlanetData GetPlanetData(Planet planet)
{
if (!planetMap.ContainsKey(planet))
throw new ArgumentOutOfRangeException("planet", "Unknown Planet");
return planetMap[planet];
}
#region Nested type: PlanetData
public class PlanetData
{
public PlanetData(double mass, double radius)
{
Mass = mass;
Radius = radius;
}
public double Mass { get; private set; }
public double Radius { get; private set; }
}
#endregion
}
}