How to map ID values (int) to String values in C# - c#

I have a list of IDs that represent different things. I want to display these IDs as strings.
For example the number 12 would map to "foo", and 13 would map to "bar"
more specifically i'm attempting to migrate the model properties to a list with the transformed string values. Here is what I have currently
var model = new PaymentExclusionModel
{
ExclusionList = response.Exclusions,
};
List<PaymentProcessorExclusionModel> list = new List<PaymentProcessorExclusionModel>();
list = response.Exclusions.Select(x => new PaymentProcessorExclusionModel
{
// Convert
});
I know what values to map the integers to strings, since it's a small set of values. I have attempted to use a Dictionary<int, string> but I get an error complaining about converting an int? to int (int? is from metadata dll and I can't change that).
Here is the code:
var model = new PaymentExclusionModel
{
ExclusionList = response.Exclusions,
};
List<PaymentProcessorExclusionModel> list = new List<PaymentProcessorExclusionModel>();
list = response.Exclusions.Select(x => new PaymentProcessorExclusionModel
{
PaymentType = dict[x.PaymentTypeId.Value]
});
Error Code:
Error CS0266 Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<DK.Fin.Secure.Mjolnir.Web.Models.PaymentProcessorExclusionModel>' to 'System.Collections.Generic.List<Web.Models.PaymentProcessorExclusionModel>'. An explicit conversion exists (are you missing a cast?)

Here is an enum-style solution:
public class MyType : EnumClass
{
public static MyType FOO = new MyType(12, "Foo");
public static MyType BAR = new MyType(13, "Bar");
public static MyType X = new MyType(99, "X");
[JsonConstructor]
private MyType(int id, string name)
{
_id = id;
_name = name;
}
public static MyType GetMyType(int id)
{
switch (id)
{
case 12: return FOO;
case 13: return BAR;
case 99: return X;
default: return null;
}
}
public static IEnumerable<MyType> GetMyTypes()
{
return new List<MyType>
{
FOO,
BAR,
X
};
}
}
I've used this type of thing with GREAT success. I had a poker app and in that case the 'MyType' was 'Card' with all of the attributes of a playing card (suit, number, etc).
The EnumClass
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyProj.MyPath
{
public abstract class EnumClass
{
protected int _id;
protected string _name;
public virtual int ID
{
get { return _id; }
}
public virtual string Name
{
get { return _name; }
}
public override string ToString()
{
return Name;
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is EnumClass)
return ID == (obj as EnumClass).ID;
return base.Equals(obj);
}
public static bool operator ==(EnumClass left, EnumClass right)
{
if ((left as Object) == null)
return (right as Object) == null;
return left.Equals(right);
}
public static bool operator !=(EnumClass left, EnumClass right)
{
if ((left as Object) == null)
return (right as Object) != null;
return !left.Equals(right);
}
}
}

I don't really get why but i believe you want to create an array map between string and int values.
First you would want to use a dictionary
Dictionary<int, string> map = new Dictionary<int, string>();
Then simply add your values
map.Add(12, 'foo');
map.Add(13, 'bar');
Then basically all you have to do is call the dictionary like an array
list = response.Exclusions.Select(x => new PaymentProcessorExclusionModel
{
NewId = map.ContainsKey(x) ? map[x] : "defaultkey?"
});
This is obviously the way to go. Try creating your 'list' variable dynamically like so
var list = response.Exclusions.Select(x => new PaymentProcessorExclusionModel
{
PaymentType = dict[x.PaymentTypeId.Value]
});

Related

When using GetPosition for ArrayAdapter, Object with Equal Fields Return UnEqual

I have created a dropdown in Android Xamarin from which I would like to auto select a dropdown value on page load. When the page is launched, it is passed a MyClass object with a Num value of 52. My dropdown has been passed an ArrayAdapter which has a list of MyClass objects, one of which has a Num value of 52. When I try to mySpinner.SetSelection(recommendedPosition); it is not working because myClassAdapter.GetPosition(recommendedValue) returns -1
I thought that the example in the following article (which shows me how to override the Equals and Hash functions of MyClass) would help me, but it still results in -1 being returned. It seems that these overridden functions are not being hit when I place a breakpoint on them.. but I understood that the GetPosition method calls IndexOf which should result in my overriden functions being called.
https://www.javaworld.com/article/3305792/comparing-java-objects-with-equals-and-hashcode.html
In this example, MyClass has one field of Num.. but my code has many more properties in MyClass, all of which are equal between recommendedValue and one of the items in listMyClass.
In InstantiateItem of my ViewPager I call:
var recommendedValue = new MyClass("52");
var List<MyClass> listMyClass = new List<MyClass> { new MyClass("52"), new MyClass("46") };
mySpinner = view.FindViewById<Spinner>(Resource.Id.mySpinner);
var myClassAdapter = new MyClassAdapter(view.Context, listMyClass);
mySpinner.Adapter = myClassAdapter;
//*
// I need the following to not return -1
//*
var recommendedPosition = myClassAdapter.GetPosition((MyClass)recommendedValue);
MyClass.cs
public class MyClass: Java.Lang.Object
{
private string Num { get; set; } = "";
public MyClass() {}
public MyClass(int? num)
{
Num = String.IsNullOrEmpty(num) ? "" : num;
}
public string GetNum()
{
return Num;
}
public override bool Equals(object other)
{
return Equals(other as MyClass);
}
public bool Equals(MyClass otherItem)
{
if (otherItem == null)
{
return false;
}
return otherItem.Num == Num;
}
public override int GetHashCode()
{
int hash = 19;
hash = hash * 31 + (Num == null ? 0 : Num.GetHashCode());
return hash;
}
}
Maybe you could consider not using GetPosition method,you could directly get the position like this :
var recommendedValue = new MyClass("52");
var List<MyClass> listMyClass = new List<MyClass> { new MyClass("52"), new MyClass("46") };
var recommendedPosition = listMyClass.IndexOf(recommendedValue);
or you could define a GetSelectPosition(MyClass myClass) in your MyClassAdapter :
class MyClassAdapter: ArrayAdapter<MyClass >
{
public Context context;
public List<MyClass> list;
...
public int GetSelectPosition(MyClass myClass)
{
return list.IndexOf(myClass);
}
}
then you could call like :
var recommendedPosition = myClassAdapter.GetSelectPosition(recommendedValue);

Method to Return list with different type

I am trying to make search with MVC 5, I got the logic but what I need is to have method that returns 3 different list objects
I have view model in MVC 5
public class SearchViewModel
{
public List<Article> ArticleSearch { get; set; }
public List<Albums> AlbumsSearch { get; set; }
public List<Blog> BlogSearch { get; set; }
public List<PDFModel> PDFSearch { get; set; }
}
In my controller:
public ActionResult Index()
{
using (DBContext db = new DBContext())
{
string xyz = "f";
var Search = new SearchViewModel()
{
AlbumsSearch = helper.Search(db.Albums, xyz) <!-- Here i get error -->
};
return View(Search);
}
}
the helper.Search :
public static IList Search(object obj,string SearchString)
{
using (DBContext db = new DBContext())
{
if (obj is Albums)
{
var AlbumSearch = db.Albums.Where(x => x.AlbumName.Contains(SearchString) ||
x.AlbbumSmallDesc.Contains(SearchString) || x.AlbumNameEnglish.Contains(SearchString)||
x.AlbbumSmallDescEnglish.Contains(SearchString)).ToList();
return AlbumSearch as List<Albums>;
}
else if(obj is Article)
{
var ArticleSearch = db.Article.Where(x => x.ArticleSubject.Contains(SearchString)||x.ArticleSubjectEnglish.Contains(SearchString)||
x.ArticleMessage.Contains(SearchString)||x.ArticleMessageEnglish.Contains(SearchString)).ToList();
return ArticleSearch;
}
else
{
return null;
}
}
}
I want to make a method that returns a list to make the search
I get this error:
Cannot implicitly convert type 'System.Collections.IList' to 'System.Collections.Generic.List<MohamedJibril.Models.Albums>'. An explicit conversion exists (are you missing a cast?)
You should probably use a different search function for different classes. Having said that, you can use the same search function with a bit of a hack. Define an extension method to convert your List<T> into a List<object>. Then just return a List<object> from your method.
public static class ListExtensions
{
public static List<Object> ToObjectList<T>(this List<T> list) where T : class
{
var objectList = new List<object>();
list.ForEach(t => objectList.Add(t));
return objectList;
}
}
List<object> Search(int i)
{
if (i == 0)
{
return new List<Album>().ToObjectList();
}
else
{
return new List<Article>().ToObjectList();
}
}
Update
You can then convert back when you're calling the search using an extension method like this.
public static List<T> FromObjectList<T>(this List<object> list) where T : class
{
var result = new List<T>();
list.ForEach(t => result.Add(t as T));
return result;
}
List<Album> albums = Search(0).FromObjectList<Album>();
The message is pretty clear, you are trying to convert IList to IList<T>. Your method returns the former and your view model has the latter.
Your search method gives you no advantage. You have all those if statements. What are you going to do if you need 30 different types, write 30 different if statements? Instead do this:
public static IList<Album> SearchAlbums(string SearchString)
{
using (DBContext db = new DBContext())
{
var AlbumSearch = db.Albums.Where(x => x.AlbumName.Contains(SearchString) ||
x.AlbbumSmallDesc.Contains(SearchString) || x.AlbumNameEnglish.Contains(SearchString) ||
x.AlbbumSmallDescEnglish.Contains(SearchString)).ToList();
return AlbumSearch;
}
}
Create the same methods for the other types too.

Custom attributes in c# for data types

I am thinking about making a custom attribute so that when we are using multiple data readers [SqldataReader] on different objects/tables, we could use the attribute to get the type of the property, and the "columnName" of the property. This way, we could then have a method that takes the data reader as a param, and from there could reflect the attributes to read in the columns. An example of what is currently being done is below, and then an example of what I am trying to accomplish. The problem I am having, is how to manage how to tell it what the (Type) is.
private static App GetAppInfo(SqlDataReader dr)
{
App app = new App();
app.ID = MCCDBUtility.GetDBValueInt(dr, "APPLICATION_ID");
app.Name = MCCDBUtility.GetDBValueString(dr, "APPNAME");
app.Desc = MCCDBUtility.GetDBValueString(dr, "APPDESCRIPTION");
app.Version = MCCDBUtility.GetDBValueString(dr, "APP_VERSION");
app.Type = MCCDBUtility.GetDBValueString(dr, "APPLICATIONTYPEID");
app.AreaName = MCCDBUtility.GetDBValueString(dr, "AREANAME");
return app;
}
What I am thinking though, so if I had a class for example like so:
[DataReaderHelper("MethodNameToGetType", "ColumnName")]
public string APPNAME {get;set;}
How could I go about this?
Fist of all, this is possible and if you like I could add a code sample.
But: This is not a good idea.
Why, you ask?
First - DataReader provides you with a method GetSchemaTable() which contains a property DataType which is a System.Type object. So basically you could create a MCCDBUtility.GetValue(dr, "columnName") that does the logic for your.
Second - What about you have a int property on your object but your datareader returns a decimal. For that case you can use Convert.ChangeType(value, type)
If you combine that you can achive what you want with
instance.Id = MCCDBUtility.GetValue<int>(dr, "columnName")
public T GetValue<T>(IDataReader reader, string columnName)
{
object value GetValue(reader, columnName);
return Convert.ChangeType(value, typeof(T));
}
private object GetValue(IDataReader reader, string columnName)
{
var schmema = reader.GetSchemaTable();
var dbType = typeof(object);
foreach(DataRowView row in schema.DefaultView)
if (row["columnName"].ToString().Equals(columnName, StringComparer.OrdinalIgnoreCase))
return row["ColumnType"];
if (dbType.Equals(typeof(int))
return GetInt(reader, columnName)
... // you get the point
else
return GetObject(reader, columnName);
}
And Third - Don't do this anyway there are great tools for mapping your query to your business objects. I don't want to name them all but a very lightweight and easy to understand is Dapper.NET, give it a try. https://github.com/StackExchange/dapper-dot-net
In combination with https://github.com/tmsmith/Dapper-Extensions you can easily map your database queries to your pocos
Update
As promised, here is the code for implementing on your own. Just create a Visual Studio Test project, insert the code and let it run. For readablity I omitted the unused IReadReader interface implementations, so you have to let intellisense create them for you.
Run the test and enjoy.
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var values = new Dictionary<string, object>();
values.Add("ProductId", 17);
values.Add("ProductName", "Something");
values.Add("Price", 29.99M);
var reader = new FakeDataReader(values);
var product1 = new Product();
reader.SetValue(product1, p => p.Id);
reader.SetValue(product1, p => p.Name);
reader.SetValue(product1, p => p.Price);
Assert.AreEqual(17, product1.Id);
Assert.AreEqual("Something", product1.Name);
Assert.AreEqual(29.99M, product1.Price);
var product2 = new Product();
reader.SetAllValues(product2);
Assert.AreEqual(17, product2.Id);
Assert.AreEqual("Something", product2.Name);
Assert.AreEqual(29.99M, product2.Price);
}
}
public class Product
{
[Mapping("ProductId")]
public int Id { get; set; }
[Mapping("ProductName")]
public string Name { get; set; }
public decimal Price { get; set; }
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
public class MappingAttribute : Attribute
{
public MappingAttribute(string columnName)
{
this.ColumnName = columnName;
}
public string ColumnName { get; private set; }
}
public static class IDataReaderExtensions
{
public static void SetAllValues(this IDataReader reader, object source)
{
foreach (var prop in source.GetType().GetProperties())
{
SetValue(reader, source, prop);
}
}
public static void SetValue<T, P>(this IDataReader reader, T source, Expression<Func<T, P>> pe)
{
var property = (PropertyInfo)((MemberExpression)pe.Body).Member;
SetValue(reader, source, property);
}
private static void SetValue(IDataReader reader, object source, PropertyInfo property)
{
string propertyName = property.Name;
var columnName = propertyName;
var mapping = property.GetAttribute<MappingAttribute>();
if (mapping != null) columnName = mapping.ColumnName;
var value = reader.GetValue(reader.GetOrdinal(columnName));
var value2 = Convert.ChangeType(value, property.PropertyType);
property.SetValue(source, value2, null);
}
}
public static class ICustomFormatProviderExtensions
{
public static T GetAttribute<T>(this ICustomAttributeProvider provider)
{
return (T)provider.GetCustomAttributes(typeof(T), true).FirstOrDefault();
}
}
public class FakeDataReader : IDataReader
{
private Dictionary<string, object> values;
public FakeDataReader(Dictionary<string, object> values)
{
this.values = values;
}
public int GetOrdinal(string name)
{
int i = 0;
foreach (var key in values.Keys)
{
if (key.Equals(name, StringComparison.OrdinalIgnoreCase)) return i;
i++;
}
return -1;
}
public object GetValue(int i)
{
return values.Values.ToArray()[i];
}
}

customized array with default value in C#

I'm trying to costruct a custimzed array - which has some properties - and which has two possibilites.
The first when I DO NOT add index, then return a a specified value, something like this:
// in this case I would like to return the 0 index item
Debug.Print(spca)
but in this case:
// in this case it will return the 1 index item fo the array
Debug.Print(spca[1])
and in this case:
//will return the 2 index item fo the array
Debug.Print(spca[2])
So the concept is if I would like to reach the 0 index item, it can be done without index input.
Can not be done using only an array. You'd need to create your own class and an indexer property public ... this[int index].
However, the requirement of using the first value if no index is given can not be solved, as this would be equal to "not calling a method", which can not be done.
Based on the comments by crashmstr you could try the following (I currently cant test it, but you should get it to work):
public class SpecialArray
{
private int[] m_theArray;
public SpecialArray(int size)
{
m_theArray = new int[size];
}
public int this[int index]
{
get { return m_theArray[index]; }
set { m_theArray[index] = value; }
}
// Please note that this override is NOT INTUITIVE TO THE USER
// Who would expect that the string representation of the array
// might be the string representation of its first element?
public override string ToString()
{
return m_theArray[0].ToString();
}
public static implicit operator int(SpecialArray a)
{
return m_theArray[0];
}
}
With this class, the following should work:
SpecialArray arr = new SpecialArray(5);
arr[0] = 10;
arr[1] = 9;
arr[2] = 8;
arr[3] = 7;
arr[4] = 6;
int elemZero = arr; // equal to arr[0]
int elemOne = arr[1];
...
using System;
using System.Collections.Generic;
using System.Windows.Forms;
public class spca
{
public static void Main(string[] args)
{
var spacs = new spca();
spacs[0] = "t";
spacs[1] = "tt";
spacs[1] = "ttt";
// this will call the overritten Tostring and deliver first value
Console.WriteLine(spacs);
Console.WriteLine(spacs[1]);
Console.WriteLine(spacs[2]);
}
private Dictionary<int, string> _innerDictionary = new Dictionary<int, string>();
public override string ToString()
{
return _innerDictionary[0];
}
public object this[int key]
{
get
{
if (_innerDictionary.ContainsKey(key)) return _innerDictionary[key];
return null;
}
set
{
_innerDictionary[key] = (string)value;
}
}
}

Extending Enums, Overkill?

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
}
}

Categories