How to maintain reference to old values in a property - c#

I have two variables, State and Address.
Depending on a condition, the value of State will = the value of Address. The problem I am having is maintaining a reference to State's previous value. I will need State's original value, should the condition change. As of now, my application cannot maintain a reference to old values.
Some more detail:
The data I am dealing with is Accounts and People (properties: Name, State, Address, Email) associated with these Accounts.
The data is displayed in a Matrix and the values are dependent on the aforementioned condition. The condition is which Account we are dealing with.
When there is a single Account, the value of State will = the value of Address. However, when there are more than 1 Accounts, the value of State will be unique.
The user can toggle between 4 Accounts. There can be multiple Accounts set at a time.
The problem is if I am dealing with a single Account and the State value becomes the Address value, I am not able to stash the old State value. The original State value will be used if there are more than 1 Account. When I have multiple Accounts, however, the State value is still = to the Address value. This is not ideal.
This is what I am looking for:
Example
Original Data = {"Ken, "WA", "123 St.", "Ken#email.com"}
Single Account
Name State Address Email
"Ken" "WA" "WA" "Ken#email.com"
Multiple Accounts
Name State Address Email
"Ken" "WA" "123 St." "Ken#email.com"
However, this is what I am getting:
Single Account - FINE
Name State Address Email
"Ken" "WA" "WA" "Ken#email.com"
Multiple Accounts - BAD
Name State Address Email
"Ken" "WA" "WA "Ken#email.com"
It is not cycling the value back
This is my code:
private void RefreshData()
{
List<string> states = new List<string>();
People.ForEach(p => states.Add(p.State)); // Attempting to stash current State vaues
bool singleAccount = Accounts.Where(a => a.IsActive).Count() == 1;
if (singleAccount)
{
int singleAccount = Accounts.IndexOf(Accounts.Where(a => a.IsActive).FirstOrDefault()) + 1; // Accounts are 1, 2, 3, or 4. I need the data for specific Account #s when a single Account
AssignStateToAddress(states, singleAccount);
}
else
{
// Use the original values here, since there are multiple Accounts.
}
}
private void AssignStateToAddress(List<string> States, int singleAccount)
{
int position = 0;
switch (singleAccount)
{
case 1: People.ForEach(p => p.Address1 = States[position++]);
break;
case 2: People.ForEach(p => p.Address2 = States[position++]);
break;
case 3: People.ForEach(p => p.Address3 = States[position++]);
break;
default: People.ForEach(p => p.Address4 = States[position++]);
break;
}
}
}
}
}

According to the OOP concept of encapsulation, this type of logic should probably be handled internally by your People class. There are several ways you could handle it, but probably the easiest is to make Address a method that understands the desired behavior.
public class People
{
public string State { get; set; }
private string _address;
public void SetAddress(string address)
=> _address = address;
public void GetAddress(bool forSingleAccount)
=> forSingleAccount ? State : _address;
}
This way "it just works" when you call GetAddress.
If you really need Address to be a property (for serialization, for example), then you can still encapsulate the behavior by adding an IsSingleAccount boolean property to People and make the Address getter check that and return one value or the other.
public class People
{
public bool IsSingleAccount { get; set; }
public string State { get; set; }
private string _address;
public string Address
{
set => _address = value;
get => IsSingleAccount ? State : _address;
}
}
That approach isn't as "clean" because People needs to track information about stuff that is an external concern, and your consuming code needs to go to extra steps to change that property on all instances. This is an example of a design problem known as a "leaky abstraction".

Related

Trying to understand Generic List in C# [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 months ago.
Improve this question
I hope everyone is well.
I have some practice code here that I've been working on and the code works and I have no issues with it however, I don't find that I fully understand what I've written and why it works.
I want to try and be able to understand my work so that I can become a better programmer, I've left comments for the code that I dont fully understand, the rest I am comfortable with.
I would appreciate any one with the spare time to give me a few pointers and some help, thank you very much.
namespace GenericList
{
class Program
{
static void Main(string[] args)
{
/* I understand how objects work, but I dont fully understand
what is happening when I am passig through my constructer
name through the List */
List<Cities> cities = new List<Cities>();
/* I am passing matching arguments through each instance
of my cities List object but I still struggle to
visualise this process */
cities.Add(new Cities(1, "Durban - Home to the largest harbor in Africa"));
cities.Add(new Cities(2, "Johannesburg - The largest city in the country"));
cities.Add(new Cities(3, "Gqebetha - Also known as P.E, the friendly city"));
cities.Add(new Cities(4, "Bloemfontien - Host of the Rose Festival"));
cities.Add(new Cities(5, "Pretoria - South Africa's capital"));
Console.WriteLine("Which city would you like to know an interesting fact for?" +
"\n1) Durban" +
"\n2) Johannesburg" +
"\n3) Gqebetha" +
"\n4) Bloemfontien" +
"\n5) Pretoria" +
"\nEnter the number for the city you want:");
int answer = int.Parse(Console.ReadLine());
bool found = false;
for (int i = 0; i < cities.Count; i++)
{
if (cities[i].Id.Equals(answer))
{
Console.WriteLine("\nANSWER: " + cities[i].City);
found = true;
}
}
if (!found)
{
Console.WriteLine("\nWe couldn't find what you are looking for.");
}
}
}
class Cities
{
int id;
string city;
public Cities(int id, string city)
{
this.id = id;
this.city = city;
}
public int Id { get => id; set => id = value; }
public string City { get => city; set => city = value; }
}
}
List<Cities> cities = new List<Cities>();
Here you are creating a List, which contains only the object of Cities's class.
List is a array type data structure with various utilities(method).
Here you are using Add(), which append a new object to the list (in that case only Cities object as you declare in first line List<Cities>).
cities.Add(new Cities(1, "Durban - Home to the largest harbor in Africa"));
If I break down this line this will be:
// Creating a new Cities object
var newCity = new Cities(1, "Durban - Home to the largest harbor in Africa");
cities.Add(newCity );
Last line from the code block is appending newCity to the list of cities
Hope you understand now, If anything left unclear let me know
i rewrote with linq so you can see as a different approach. more easy reading code
void Main()
{
List<Cities> cities = new List<Cities>();
//add to cities array a new City class, since constructor accept 2 parameters you supply them on new object creation
Cities city;
city = new Cities(1, "Durban - Home to the largest harbor in Africa");
cities.Add(city);
city = new Cities(2, "Johannesburg - The largest city in the country");
cities.Add(city);
city = new Cities(3, "Gqebetha - Also known as P.E, the friendly city");
cities.Add(city);
city = new Cities(4, "Bloemfontien - Host of the Rose Festival");
cities.Add(city);
city = new Cities(5, "Pretoria - South Africa's capital");
cities.Add(city);
//different approach: create list with objects
cities = new List<Cities>()
{
new Cities(1, "Durban - Home to the largest harbor in Africa"),
new Cities(2, "Johannesburg - The largest city in the country"),
new Cities(3, "Gqebetha - Also known as P.E, the friendly city"),
new Cities(4, "Bloemfontien - Host of the Rose Festival"),
new Cities(5, "Pretoria - South Africa's capital")
};
Console.WriteLine("Which city would you like to know an interesting fact for?" +
"\n1) Durban" +
"\n2) Johannesburg" +
"\n3) Gqebetha" +
"\n4) Bloemfontien" +
"\n5) Pretoria" +
"\nEnter the number for the city you want:");
int answer = int.Parse(Console.ReadLine());
var result = cities
.Select((obj, index) => new { index, obj }) //set index for each object
.Where(w => w.index == answer)
.FirstOrDefault();
if (result == null)
Console.WriteLine("\nWe couldn't find what you are looking for.");
else
Console.WriteLine("\nANSWER: " + result.obj.City);
}
class Cities
{
//class properties
public int Id { get; set; }
public string City { get; set; }
//Parameterized Constructor https://www.tutlane.com/tutorial/csharp/csharp-constructors-with-examples#divcspzcst
public Cities(int id, string city)
{
Id = id;
City = city;
}
}
With
List<Cities> cities = new List<Cities>();
you create a new List-object that can store Cities objects. The generic type parameter <Cities> denotes the type of the list items and does not refer to the constructor. As an example, the following code would create a list that could store integer values:
List<int> lst = new List<int>();
The List<T> class is a generic type. The basic operations for a list like adding, removing, enumerating and so on are the same no matter what the type of the list items is. By creating a generic type, you can implement the functionality without knowing which types are used later on when you create an object. You might compare this to a method: when you implement the method, you define the parameters and their types; when you call the method, you supply the values of the parameters.
The code in the following line performs two tasks:
cities.Add(new Cities(1, "Durban - Home to the largest harbor in Africa"));
First, a new object of type Cities is created and initialized through the constructor. Second, it is added to the list. These are two separate steps that can also written like this:
// Create new object of type Cities
var city = new Cities(1, "Durban - Home to the largest harbor in Africa");
// Add newly created object to list
cities.Add(city);
As #BinRohan suggested in the comments, it might be a good idea the rename the Cities class to City because it defines a single city, not a collection of cities.
When you write this line of code
List<Cities> cities = new List<Cities>();
You instantiate an object being a List of "Cities". List is a "generic" type. It means it is able to handle any type and will behave the same. You could have a List or List, you'll manipulate different objects but the behaviour of List remains the same. You are not "passing" Cities to the List constructor, consider List as a type in itself.
It would be equivalent of declaring an array of Cities for example. There is no data in your list yet but it is ready to receive multiple instance of Cities.
Then when you write
cities.Add(New Cities{prop1=value,prop2=value...});
at run time it will do something like
var c = new Cities();
c.prop1=value;
c.prop2=value;
cities.Add(c);
It's kind of a shortcut which also make the code more readable.

Switch Statement To Dynamic for filling in parameters

I am trying to create a more dynamic approach to pulling data for a view than a switch statement. Right now I have several different options and more could be added anytime. The tables that will be pulled from are all the same in format except for the name of the table and the name of their ID field.
public List<listoftables> BuildListOfTables(string router)
{
var listOfViewModels = new List<FormatOfTables>();
using var context = new TableContext();
switch (router)
{
case "firstTable":
listOfViewModels = context.Set<firstTable>().Select(x => new FormatOfTables
{
UniqueID = x.FirstTableID,
Value = x.Value,
}).ToList();
break;
case "secondTable":
listOfViewModels = context.Set<secondTable>().Select(x => new FormatOfTables
{
UniqueID = x.SecondTableID,
Value = x.Value,
}).ToList();
break;
case "thirdTable":
listOfViewModels = context.Set<ThirdTable>().Select(x => new FormatOfTables
{
UniqueID = x.ThirdTableID,
Value = x.Value,
}).ToList();
break;
return listOfViewModels;
}
I'm trying to find a way to do this more dynamically. So as long as the option in router matches a table name, each table that gets put into the model just fills the UniqueID and Value to be whatever their ID and value happens to be rather than having to match the column names. So if a fourth table came in I would only have to worry about if router matched the table name rather than having to add an entirely new switch per entry.
The tables that will be pulled from are all the same in format except for the name of the table and the name of their ID field.
The problem here is passing the typename to the Set<T>() function. But we'll get to that. First, let's make some of this easier by adding an interface:
public interface IMyTable
{
string TableName {get;}
int UniqueID {get;}
}
Then each of your firstTable, secondTable, ThirdTable types must implement this interface:
public class firstTable : IMyTable
{
// existing class stuff here
public int UniqueID { get { return FirstTableId;} }
public string TableName { get { return "FirstTable"; } }
}
And now the method can look like this:
public IEnumerable<listoftables> BuildListOfTables(string router)
{
using var context = new TableContext();
DBSet tableSet = null; //I'm making an assumption about the Set() function here. You may need to change the type.
switch (router)
{
case "firstTable":
tableSet = context.Set<firstTable>();
break;
case "secondTable":
tableSet = context.Set<secondTable>();
break;
case "thirdTable":
tableSet = context.Set<ThirdTable>();
break;
}
if (tableSet != null)
{
return tableSet.Select(x => new FormatOfTables
{
UniqueID = x.UniqueID,
Value = x.Value
});
}
return null;
}
This reduces the repeated boilerplate down to just as much as is necessary to call the generic Set<>() function.
From here we can further reduce the code by changing how the function is designed, including how you expect to call it:
public IEnumerable<listoftables> BuildListOfTables<T>() where T : IMyTable
{
using var context = new TableContext();
return context.Set<T>()
.Select(x => new FormatOfTables
{
UniqueID = x.UniqueID,
Value = x.Value
});
}
But all this really does is push where you have to put the switch() statement up to the call site. However, that might be worth it if the call site happens to have the type information already available.
Note for ALL of these examples I converted the method to return IEnumerable instead of a List. Calling .ToList() can be notoriously bad for performance. If you really need a list (hint: you usually don't) you can still put the ?.ToList() after the function call. You might be able to improve things even further in this case by returning IQueryable, which could let later code continue the expression tree before executing anything on the server.

Trying to get NetSuite Country list with enumeration value linked to code and name

I am implementing a integration with NetSuite in C#. In the external system I need to populate a list of countries that will match NetSuite's country list.
The NetSuite Web Service provides an enumeration call Country
public enum Country {
_afghanistan,
_alandIslands,
_albania,
_algeria,
...
You can also get a list of country Name and Code (in an albeit not so straight forward way) from the web service. (See: http://suiteweekly.com/2015/07/netsuite-get-all-country-list/)
Which gives you access to values like this:
Afghanistan, AF
Aland Islands, AX
Albania, AL
Algeria, DZ
American Samoa, AS
...
But, as you can see, there is no way to link the two together. (I tried to match by index but that didn't work and sounds scary anyway)
NetSuite's "help" files have a list. But this is static and I really want a dynamic solution that updates as NetSuites updates because we know countries will change--even is not that often.
Screenshot of Country Enumerations from NetSuite help docs
The only solutions I have found online are people who have provided static data that maps the two sets of data. (ex. suiteweekly.com /2015/07/netsuite-complete-country-list-in-netsuite/)
I cannot (don't want to) believe that this is the only solution.
Anyone else have experience with this that has a better solution?
NetSuite, if you are reading, come on guys, give a programmer a break.
The best solution I have come up with is to leverage the apparent relationship between the country name and the enumeration key to forge a link between the two. I am sure others could improve on this solution but what I would really like to see is a solution that isn't a hack like this that relies on an apparent pattern but rather on that is based on an explicit connection. Or better yet NetSuite should just provide the data in one place all together.
For example you can see the apparent relationship here:
_alandIslands -> Aland Islands
With a little code I can try to forge a match.
I first get the Enumeration Keys into an array. And I create a list of objects of type NetSuiteCountry that will hold my results.
var countryEnumKeys = Enum.GetNames(typeof(Country));
var countries = new List<NetSuiteCountry>();
I then loop through the list of country Name and Code I got using the referenced code above (not shown here).
For each country name I then strip all non-word characters from the country name with Regex.Replace, prepend an underscore (_) and then convert the string to lowercase. Finally I try to find a match between the Enumeration Key (converted to lowercase as well) and the matcher string that was created. If a match is found I save all the data together the countries list.
UPDATE: Based on the comments I have added additional code/hacks to try to deal with the anomalies without hard-coding exceptions. Hopefully these updates will catch any future updates to the country list as well, but no promises. As of this writing it was able to handle all the known anomalies. In my case I needed to ignore Deprecated countries so those aren't included.
foreach (RecordRef baseRef in baseRefList)
{
var name = baseRef.name;
//Skip Deprecated countries
if (name.EndsWith("(Deprecated)")) continue;
//Use the name to try to find and enumkey match and only add a country if found.
var enumMatcher = $"_{Regex.Replace(name, #"\W", "").ToLower()}";
//Compares Ignoring Case and Diacritic characters
var enumMatch = CountryEnumKeys.FirstOrDefault(e => string.Compare(e, enumMatcher, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase) == 0);
//Then try by Enum starts with Name but only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => e.ToLower().StartsWith(enumMatcher));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 1 : ");
enumMatch = matches.First();
}
}
//Then try by Name starts with Enum but only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => enumMatcher.StartsWith(e.ToLower()));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 2 : ");
enumMatch = matches.First();
}
}
//Finally try by first half Enum and Name match but again only one.
if (enumMatch == null)
{
var matches = CountryEnumKeys.Where(e => e.ToLower().StartsWith(enumMatcher.Substring(0, (enumMatcher.Length/2))));
if (matches.Count() == 1)
{
Debug.Write($"- Country Match Hack 3 : ");
enumMatch = matches.First();
}
}
if (enumMatch != null)
{
var enumIndex = Array.IndexOf(CountryEnumKeys, enumMatch);
if (enumIndex >= 0)
{
var country = (Country) enumIndex;
var nsCountry = new NetSuiteCountry
{
Name = baseRef.name,
Code = baseRef.internalId,
EnumKey = country.ToString(),
Country = country
};
Debug.WriteLine($"[{nsCountry.Name}] as [{nsCountry.EnumKey}]");
countries.Add(nsCountry);
}
}
else
{
Debug.WriteLine($"Could not find Country match for: [{name}] as [{enumMatcher}]");
}
}
Here is my NetSuiteCountry class:
public class NetSuiteCountry
{
public string Name { get; set; }
public string Code { get; set; }
public string EnumKey { get; set; }
public Country Country { get; set; }
}
Let me start off with a disclaimer that I'm not a coder, and this is the first day I've tried to look at a C# program.
I need something similar for a Javascript project where I need the complete list of Netsuite company names, codes and their numeric values and when reading the help it seemed like the only way was through webservices.
I downloaded the sample application for webservices from Netsuite and a version of Visual Studio and I was able to edit the sample program provided to create a list of all of the country names and country codes (ex. Canada, CA).
I started out doing something similar to the previous poster to get the list of country names:
string[] countryList = Enum.GetNames(typeof(Country));
foreach (string s in countryList)
{
_out.writeLn(s);
}
But I later got rid of this and started a new technique. I created a class similar to the previous answer:
public class NS_Country
{
public string countryCode { get; set; }
public string countryName { get; set; }
public string countryEnum { get; set; }
public string countryNumericID { get; set; }
}
Here is the new code for getting the list of company names, codes and IDs. I realize that it's not very efficient as I mentioned before I'm not really a coder and this is my first attempt with C#, lots of Google and cutting/pasting ;D.
_out.writeLn(" Attempting to get Country list.");
// Create a list for the NS_Country objects
List<NS_Country> CountryList = new List<NS_Country>();
// Create a new GetSelectValueFieldDescription object to use in a getSelectValue search
GetSelectValueFieldDescription countryDesc = new GetSelectValueFieldDescription();
countryDesc.recordType = RecordType.customer;
countryDesc.recordTypeSpecified = true;
countryDesc.sublist = "addressbooklist";
countryDesc.field = "country";
// Create a GetSelectValueResult object to hold the results of the search
GetSelectValueResult myResult = _service.getSelectValue(countryDesc, 0);
BaseRef[] baseRef = myResult.baseRefList;
foreach (BaseRef nsCountryRef in baseRef)
{
// Didn't know how to do this more efficiently
// Get the type for the BaseRef object, get the property for "internalId",
// then finally get it's value as string and assign it to myCountryCode
string myCountryCode = nsCountryRef.GetType().GetProperty("internalId").GetValue(nsCountryRef).ToString();
// Create a new NS_Country object
NS_Country countryToAdd = new NS_Country
{
countryCode = myCountryCode,
countryName = nsCountryRef.name,
// Call to a function to get the enum value based on the name
countryEnum = getCountryEnum(nsCountryRef.name)
};
try
{
// If the country enum was verified in the Countries enum
if (!String.IsNullOrEmpty(countryToAdd.countryEnum))
{
int countryEnumIndex = (int)Enum.Parse(typeof(Country), countryToAdd.countryEnum);
Debug.WriteLine("Enum: " + countryToAdd.countryEnum + ", Enum Index: " + countryEnumIndex);
_out.writeLn("ID: " + countryToAdd.countryCode + ", Name: " + countryToAdd.countryName + ", Enum: " + countryToAdd.countryEnum);
}
}
// There was a problem locating the country enum that was not handled
catch (Exception ex)
{
Debug.WriteLine("Enum: " + countryToAdd.countryEnum + ", Enum Index Not Found");
_out.writeLn("ID: " + countryToAdd.countryCode + ", Name: " + countryToAdd.countryName + ", Enum: Not Found");
}
// Add the countryToAdd object to the CountryList
CountryList.Add(countryToAdd);
}
// Create a JSON - I need this for my javascript
var javaScriptSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
string jsonString = javaScriptSerializer.Serialize(CountryList);
Debug.WriteLine(jsonString);
In order to get the enum values, I created a function called getCountryEnum:
static string getCountryEnum(string countryName)
{
// Create a dictionary for looking up the exceptions that can't be converted
// Don't know what Netsuite was thinking with these ones ;D
Dictionary<string, string> dictExceptions = new Dictionary<string, string>()
{
{"Congo, Democratic Republic of", "_congoDemocraticPeoplesRepublic"},
{"Myanmar (Burma)", "_myanmar"},
{"Wallis and Futuna", "_wallisAndFutunaIslands"}
};
// Replace with "'s" in the Country names with "s"
string countryName2 = Regex.Replace(countryName, #"\'s", "s");
// Call a function that replaces accented characters with non-accented equivalent
countryName2 = RemoveDiacritics(countryName2);
countryName2 = Regex.Replace(countryName2, #"\W", " ");
string[] separators = {" ","'"}; // "'" required to deal with country names like "Cote d'Ivoire"
string[] words = countryName2.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < words.Length; i++)
{
string word = words[i];
if (i == 0)
{
words[i] = char.ToLower(word[0]) + word.Substring(1);
}
else
{
words[i] = char.ToUpper(word[0]) + word.Substring(1);
}
}
string countryEnum2 = "_" + String.Join("", words);
// return an empty string if the country name contains Deprecated
bool b = countryName.Contains("Deprecated");
if (b)
{
return String.Empty;
}
else
{
// test to see if the country name was one of the exceptions
string test;
bool isExceptionCountry = dictExceptions.TryGetValue(countryName, out test);
if (isExceptionCountry == true)
{
return dictExceptions[countryName];
}
else
{
return countryEnum2;
}
}
}
In the above I used a function, RemoveDiacritics I found here. I will repost the referenced function below:
static string RemoveDiacritics(string text)
{
string formD = text.Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
foreach (char ch in formD)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(ch);
if (uc != UnicodeCategory.NonSpacingMark)
{
sb.Append(ch);
}
}
return sb.ToString().Normalize(NormalizationForm.FormC);
}
Here are the tricky cases to test any solution you develop with:
// Test tricky names
Debug.WriteLine(getCountryEnum("Curaçao"));
Debug.WriteLine(getCountryEnum("Saint Barthélemy"));
Debug.WriteLine(getCountryEnum("Croatia/Hrvatska"));
Debug.WriteLine(getCountryEnum("Korea, Democratic People's Republic"));
Debug.WriteLine(getCountryEnum("US Minor Outlying Islands"));
Debug.WriteLine(getCountryEnum("Cote d'Ivoire"));
Debug.WriteLine(getCountryEnum("Heard and McDonald Islands"));
// Enums that fail
Debug.WriteLine(getCountryEnum("Congo, Democratic Republic of")); // _congoDemocraticPeoplesRepublic added to exceptions
Debug.WriteLine(getCountryEnum("Myanmar (Burma)")); // _myanmar added to exceptions
Debug.WriteLine(getCountryEnum("Netherlands Antilles (Deprecated)")); // Skip Deprecated
Debug.WriteLine(getCountryEnum("Serbia and Montenegro (Deprecated)")); // Skip Deprecated
Debug.WriteLine(getCountryEnum("Wallis and Futuna")); // _wallisAndFutunaIslands added to exceptions
For my purposes I wanted a JSON object that had all the values for Coutries (Name, Code, Enum, Value). I'll include it here in case anyone is searching for it. The numeric values are useful when you have a 3rd party HTML form that has to forward the information to a Netsuite online form.
Here is a link to the JSON object on Pastebin.
My appologies for the lack of programming knowledge (only really do a bit of javascript), hopefully this additional information will be useful for someone.

Map enum value robustly

I have a form where I collect data from users. When this data is collected, I pass it to various partners, however each partner has their own rules for each piece of data, so this has to be converted. I can make this happen, but my worries are about the robustness. Here's some code:
First, I have an enum. This is mapped to dropdown a dropdown list - the description is the text value, and the int mapped to the value.
public enum EmploymentStatusType
{
[Description("INVALID!")]
None = 0,
[Description("Permanent full-time")]
FullTime = 1,
[Description("Permanent part-time")]
PartTime = 2,
[Description("Self employed")]
SelfEmployed = 3
}
When the form is submitted, the selected value is converted to its proper type and stored in another class - the property looks like this:
protected virtual EmploymentStatusType EmploymentStatus
{
get { return _application.EmploymentStatus; }
}
For the final bit of the jigsaw, I convert the value to the partners required string value:
Dictionary<EmploymentStatusType, string> _employmentStatusTypes;
Dictionary<EmploymentStatusType, string> EmploymentStatusTypes
{
get
{
if (_employmentStatusTypes.IsNull())
{
_employmentStatusTypes = new Dictionary<EmploymentStatusType, string>()
{
{ EmploymentStatusType.FullTime, "Full Time" },
{ EmploymentStatusType.PartTime, "Part Time" },
{ EmploymentStatusType.SelfEmployed, "Self Employed" }
};
}
return _employmentStatusTypes;
}
}
string PartnerEmploymentStatus
{
get { return _employmentStatusTypes.GetValue(EmploymentStatus); }
}
I call PartnerEmploymentStatus, which then returns the final output string.
Any ideas how this can be made more robust?
Then you need to refactor it into one translation area. Could be something like a visitor pattern implementation. Your choices are distribute the code (as you are doing now) or visitor which would centralize it. You need to build in a degree of fragility so your covering tests will show problems when you extend in order to force you to maintain the code properly. You are in a fairly common quandry which is really a code organisational one
I did encounter such a problem in one of my projects and I solved it by using a helper function and conventions for resource names.
The function is this one:
public static Dictionary<T, string> GetEnumNamesFromResources<T>(ResourceManager resourceManager, params T[] excludedItems)
{
Contract.Requires(resourceManager != null, "resourceManager is null.");
var dictionary =
resourceManager.GetResourceSet(culture: CultureInfo.CurrentUICulture, createIfNotExists: true, tryParents: true)
.Cast<DictionaryEntry>()
.Join(Enum.GetValues(typeof(T)).Cast<T>().Except(excludedItems),
de => de.Key.ToString(),
v => v.ToString(),
(de, v) => new
{
DictionaryEntry = de,
EnumValue = v
})
.OrderBy(x => x.EnumValue)
.ToDictionary(x => x.EnumValue, x => x.DictionaryEntry.Value.ToString());
return dictionary;
}
The convention is that in my resource file I will have properties that are the same as enum values (in your case None, PartTime etc). This is needed to perform the Join in the helper function which, you can adjust to match your needs.
So, whenever I want a (localized) string description of an enum value I just call:
var dictionary = EnumUtils.GetEnumNamesFromResources<EmploymentStatusType>(ResourceFile.ResourceManager);
var value = dictionary[EmploymentStatusType.Full];

How to filter off of a country code?

Okay, here's what I'm attempting.
I have a drop down list with all of the countries in the world.
The user selects one, and that value is passed to a case statement,
if it matches the case, an email is sent.
I only have four different recipents, but I can have upwards to dozens
of countries to match with each one of the four emails. For example:
switch (selectedCountry)
{
case "ca":
sendTo = canadaEmail;
break;
case "uk":
case "de":
case "fr"
sendTo = europeanEmail;
break;
case "bb":
sendTo = barbadosEmail;
break;
default:
sendTo = usEmail;
break;
}
What I would like to know is, what's a better way of doing this rather than having
one huge case statement?
You can use a dictionary instead:
Dictionary<string, string> sendToEmails = new Dictionary<string, string>();
sendToEmails["bb"] = barbadosEmail;
sendToEmails["ca"] = canadaEmail;
sendToEmails["uk"] = europeanEmail;
sendToEmails["de"] = europeanEmail;
Then use TryGetValue to get the value when you need it:
string sendTo;
if (sendToEmails.TryGetValue(selectedCountry, out sendTo))
{
// Send the email here.
}
One advantage of this method is that your dictionary doesn't have to be hard coded into your program. It could just as easily come from a configuration file or a database.
If you choose the database route you could also consider using LINQ:
string sendTo = dc.CountryEmails
.SingleOrDefault(c => c.Country == selectedCountry);
You can't get around the fact that somewhere, somehow, you'll have to enumerate the countries and assign them to an email address. You could do that in any number of ways, be it a database, an external XML file, or an internal List object.
For example:
List<string> europeanEmailCountries = new List<string>();
europeanEmailCountries.AddRange("fr", "de"); // etc
...
if(europeanEmailCountries.Contains(countryCode))
{
sendTo = europeanEmailAddress;
}
This saves you the convoluted switch statement by allowing you to define a list of countries mapped to a particular email address, without going through each potential entry. I might be inclined to populate the list from an XML file instead of hardcoding the values, though.
A couple options that would eliminate the cases:
Store the mappings in a database and look them up by country code.
Build a map in your code with the country code as the key and the email as the value.
Externalize it (XML, database, whatever you like...) and only implement a "state machine" which chooses the right one.
Yes -- if you have a hard coded list somewhere, consider using a collection of a custom type:
public class MyType
{
public string Code { get; set; }
public string Email { get; set; }
}
Then you could do something like this:
List<MyType> myList = new List<MyType>()
{
new MyType() { Code = "A", Email = "something" },
// etc..
}
string emailAddress = myList.Where(m => m.Code == selectedCountry);
Although, I'd say this is very poor design and I would encourage you to use a rdbms of some sort.

Categories