I'm working on an online booking site (airline) and I want to validate if the user/customer's chosen route is valid according to some settings. The existing codes uses a lot of enums and I found myself doing lots of if/if else/else to map a specific enum to a particular action that I wanted to happen. What I want to do is to write a enum-specific method that would do the mapping for me. Is there any standard way to do this?
Here's a simplified version of the app code using the same class names/enum values etc from the real app:
// real app has 9 members, shortened for simplicity's sake
public enum RegionType
{
Station,
Country,
All
}
public enum Directionality
{
Between,
From,
To
}
// simplified version
public class Flight
{
public RegionType RegionType { get; set; }
public RegionType TravelRegionType { get; set; }
public string RegionCode { get; set; }
public string TravelRegionCode { get; set; }
public string RegionCountryCode { get; set; }
public string TravelRegionCountryCode { get; set; }
public Directionality Directionality { get; set; }
}
Here's some sample usage:
// valid flight
Flight flight = new Flight()
{
RegionCode = "NY",
CountryCode = "JP",
RegionType = RegionType.Station,
TravelRegionType = RegionType.Country,
Directionality = Directionality.Between
};
// these are the station code/country code that user selected
// needs to be validated against the Flight object above
var userSelectedRoutes = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("NY", "JP"),
new KeyValuePair<string, string>("NY", "AU"),
new KeyValuePair<string, string>("JP", "NY")
};
Some code validation I wrote to lessen nested if/else if/else enum matching:
private bool IsRouteValid(Directionality direction, string origin,
string destination, string departure, string arrival)
{
// both departure station and arrival station
if (direction == Directionality.Between)
{
return (origin.Equals(departure, StringComparison.OrdinalIgnoreCase)
&& destination.Equals(arrival, StringComparison.OrdinalIgnoreCase)
|| origin.Equals(arrival, StringComparison.OrdinalIgnoreCase)
&& destination.Equals(departure, StringComparison.OrdinalIgnoreCase));
}
else if (direction == Directionality.From)
{
return (origin.Equals(departure,
StringComparison.OrdinalIgnoreCase));
}
else if (direction == Directionality.To)
{
return (destination.Equals(arrival,
StringComparison.OrdinalIgnoreCase));
}
return false;
}
And here's the messy code I want to change:
if (flight.RegionType == RegionType.Station
&& flight.TravelRegionType == RegionType.Country)
{
return userSelectedRoutes.Any(route =>
IsRouteValid(flight.Directionality, route.Key, route.Value,
flight.RegionCode, flight.TravelRegionCode));
}
else if (flight.RegionType == RegionType.Country
&& flight.TravelRegionType == RegionType.Station)
{
return userSelectedRoutes.Any(route =>
IsRouteValid(flight.Directionality, route.Key, route.Value,
flight.CountryCode, flight.RegionCode));
}
else if (flight.RegionType == RegionType.Station
&& flight.TravelRegionType == RegionType.Station)
{
return userSelectedRoutes.Any(route =>
IsRouteValid(flight.Directionality, route.Key, route.Value,
flight.RegionCode, flight.TravelRegionCode));
}
else if (flight.RegionType == RegionType.Station
&& flight.TravelRegionType == RegionType.All)
{
return userSelectedRoutes.Any(route =>
IsRouteValid(flight.Directionality, route.Key, route.Value,
flight.RegionCode, route.Value));
}
else if (flight.RegionType == RegionType.All
&& flight.TravelRegionType == RegionType.Station)
{
return userSelectedRoutes.Any(route =>
IsRouteValid(flight.Directionality, route.Key, route.Value,
route.Key, flight.TravelRegionCode));
}
else if (flight.RegionType == RegionType.All
&& flight.TravelRegionType == RegionType.All)
{
return true;
}
else
{
return false;
}
Legend:
RegionCode
= departure station/origin TravelRegionCode
= arrival station/destination Between
= routes must be only from the given departure station and arrival station and vice-versa (ex NY-JP or JP-NY) From
= from particular station to any routes (ex AU-All) To
= any routes to a particular station (ex All-AU)
If you could notice, the .Any
of all the conditions above are same with slight changes. I want to reduce code redundancy, if possible. I used KeyValuePair
so I have both departure station and arrival station on a single data type.
Any ideas on how could I make this code less messy/beautiful? I know I also hard coded IsRouteValid()
but I'm 100% sure that Directionality
could only have the 3 possible combinations. RegionType
on the other hand could have several several combinations like Station-Station, Station-Country, Country-Station, Country-Country etc.
Expected Output:
Valid/True for first route (NY-JP)
Invalid/False for second route (NY-AU)
Valid/True for third route (JP-NY) [since Directionality
is Between
]
Thank you for reading this very long query and thanks in advance for your feedback and suggestions.
Similar post:
Enum and Dictionary
A way to handle such enum-Action-Mappings is using dictionaries. Here's an example:
public enum MyEnum
{
EnumValue1,
EnumValue2,
EnumValue3
}
private IDictionary<MyEnum, Action> Mapping = new Dictionary<MyEnum, Action>
{
{ MyEnum.EnumValue1, () => { /* Action 1 */ } },
{ MyEnum.EnumValue2, () => { /* Action 2 */ } },
{ MyEnum.EnumValue3, () => { /* Action 3 */ } }
};
public void HandleEnumValue(MyEnum enumValue)
{
if (Mapping.ContainsKey(enumValue))
{
Mapping[enumValue]();
}
}
Of course you can also use Func
instead of Action
to handle parameters.
Edit:
As you're not only using one enumeration but pairs of enumerations you would have to adjust the example above to maybe handle a Tuple
or another way to aggregate the enum values.
Following @MatthiasG suggestion, here's the code I'd ended up writing:
private List<KeyValuePair<RegionType, string>>
GetRegionTypeAndValueMapping(Flight flight,
RegionType regionType,
RegionType travelRegionType)
{
var mapping = new List<KeyValuePair<RegionType, string>>();
if(regionType == RegionType.Station)
{
mapping.Add(new KeyValuePair<RegionType, string>
(RegionType.Station, flight.RegionCode));
}
else if(regionType == RegionType.Country)
{
mapping.Add(new KeyValuePair<RegionType, string>
(RegionType.Country, flight.RegionCountryCode));
}
else if(regionType == RegionType.All)
{
mapping.Add(new KeyValuePair<RegionType, string>
(RegionType.All, null));
}
if(travelRegionType == RegionType.Station)
{
mapping.Add(new KeyValuePair<RegionType, string>
(RegionType.Station, flight.TravelRegionCode));
}
else if(travelRegionType == RegionType.Country)
{
mapping.Add(new KeyValuePair<RegionType, string>
(RegionType.Country,
flight.TravelRegionCountryCode));
}
else if(travelRegionType == RegionType.All)
{
mapping.Add(new KeyValuePair<RegionType, string>
(RegionType.All, null));
}
return mapping;
}
I used List<KeyValuePair<RegionType, string>>
because Dictionary
doesn't allow key duplicates. My keys are of RegionType enum
and there will be a time that both departure and arrival stations will have the same RegionType enum
. (i.e. Station-Station, Country-Country and All-All).
// Copyright (c) 2010 Alex Regueiro
// Licensed under MIT license, available at
// <http://www.opensource.org/licenses/mit-license.php>.
// Published originally at
// <http://blog.noldorin.com/2010/05/combinatorics-in-csharp/>.
// Version 1.0, released 22nd May 2010.
// modified by moi to be a generator
public static IEnumerable<T[]> GetPermutations<T>(IList<T> list,
int? resultSize,
bool withRepetition)
{
if (list == null)
{
throw new ArgumentNullException("Source list is null.");
}
if (resultSize.HasValue && resultSize.Value <= 0)
{
throw new ArgumentException("Result size must be any
number greater than zero.");
}
var result = new T[resultSize.HasValue ? resultSize.Value : list.Count];
var indices = new int[result.Length];
for (int i = 0; i < indices.Length; i++)
{
indices[i] = withRepetition ? -1 : i - 1;
}
int curIndex = 0;
while (curIndex != -1)
{
indices[curIndex]++;
if (indices[curIndex] == list.Count)
{
indices[curIndex] = withRepetition ? -1 : curIndex - 1;
curIndex--;
}
else
{
result[curIndex] = list[indices[curIndex]];
if (curIndex < indices.Length - 1)
{
curIndex++;
}
else
{
yield return result;
}
}
}
}
Okay, I cheated. :P I needed a method that will compute permutations with repetitions, so instead of writing one, I googled. (hehe) The reason why I used permutations is to avoid hard coding all the possible combinations that RegionType
might have. I modified Alex Regueiro's method to be a generator so that I could use Linq
for it. For a refresher on Permutation and Combination, see this very excellent math stackexchange post.
I modified IsRouteValid()
to handle RegionType.All
whose value is null
.
Here's the modified version:
private bool IsRouteValid(Directionality direction, string origin,
string destination, string departure,
string arrival)
{
// ** == All stations/countries
if ((origin == null && departure == "**") &&
(destination == null && arrival == "**"))
{
return true;
}
else if (origin == null && departure == "**")
{
return destination.Equals(arrival, StringComparison.OrdinalIgnoreCase);
}
else if (destination == null && arrival == "**")
{
return origin.Equals(departure, StringComparison.OrdinalIgnoreCase);
}
// both departure station and arrival station
if (direction == Directionality.Between)
{
return (origin.Equals(departure,
StringComparison.OrdinalIgnoreCase) &&
destination.Equals(arrival,
StringComparison.OrdinalIgnoreCase) ||
origin.Equals(arrival,
StringComparison.OrdinalIgnoreCase) &&
destination.Equals(departure,
StringComparison.OrdinalIgnoreCase));
}
else if (direction == Directionality.From)
{
return (origin.Equals(arrival, StringComparison.OrdinalIgnoreCase));
}
else if (direction == Directionality.To)
{
return (destination.Equals(departure,
StringComparison.OrdinalIgnoreCase));
}
return false;
}
It's show time!
RegionType[] allRegionTypes = (RegionType[])
Enum.GetValues(typeof(RegionType));
var validFlights =
GetPermutations<RegionType>(allRegionTypes, 2, true)
.Select(perm => new
{
RegionType = perm.First(),
TravelRegionType = perm.Last()
})
.Where(result => result.RegionType == flight.RegionType &&
result.TravelRegionType ==
flight.TravelRegionType)
.Select(map =>
GetRegionTypeAndValueMapping(flight,
map.RegionType,
map.TravelRegionType));
// same functionality as my previous messy code
// validates all flights selected by user
// it doesn't matter if not all flights are valid
// as long as one of them is
foreach(var validFlight in validFlights)
{
userSelectedRoutes.Any(kvp => IsRouteValid(flight.Directionality,
kvp.Key,
kvp.Value,
validFlight.First().Value,
validFlight.Last().Value))
.Dump("Any Flight");
}
I created this code to demonstrate how I got the result which is the same as the my expected results above.
foreach(var route in userSelectedRoutes)
{
foreach(var validFlight in validFlights)
{
bool condition = IsRouteValid(flight.Directionality,
route.Key,
route.Value,
validFlight.First().Value,
validFlight.Last().Value);
Console.WriteLine(string.Format("{0}-{1} {2}",
route.Key,
route.Value,
condition.ToString()));
}
}
Results:
Note:
.Dump()
is a Linqpad extension.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With