Reputation: 17
I'm learning C# for a course I'm taking. My current assignment is to build a gym membership form. I have it working but I keep thinking there must be a better way to do one of my functions.
I have a checklist of extras for the gym:
Each has a value associated. If the diet is checked then $20 is added onto the base membership etc. Currently, I have it set up like this (which works):
private double calculateExtra()
{
//this section adds extra charges to the base membership cost
extraCost = 0;
if (accessCheck.Checked)
{
extraCost += 1;
}
if (personalTrainerCheck.Checked)
{
extraCost += 20;
}
if (dietConsultCheck.Checked)
{
extraCost += 20;
}
if (fitnessVidCheck.Checked)
{
extraCost += 2;
}
return extraCost;
}
Can someone please help me understand how to change this into a for loop or a foreach loop? I've been searching; I tried doing it as an array, but got confused with the values needing to be added, etc.
The output goes into a text box on a form once a calculate button has been pressed.
Upvotes: 0
Views: 161
Reputation: 32223
Another example, using a specialized class object that handles (or, can be easily modified to) all the operations related to the selection of these CheckBoxes.
The class object is build using an IEnumerable((CheckBox, decimal))
and creates a Dictionary<CheckBox, Func<decimal>
out of it.
The Func<decimal>
returns the value associated to a CheckBox and performs other operations if needed or requested. In this case, as an example, it adds the VAT to the price if the AddVAT
property is set to true. This property can be changed at any time and all values will be recalculated and returned by the public GetSelectedServicesExtras()
method.
private class ExtraServices
{
private Dictionary<CheckBox, Func<decimal>> services = null;
public ExtraServices(IEnumerable<(CheckBox uiElement, decimal assocPrice)> servicePrices)
{
services = new Dictionary<CheckBox, Func<decimal>>();
foreach (var service in servicePrices) {
services.Add(service.uiElement, ()=>
AddVAT ? service.assocPrice + (service.assocPrice * VAT / 100)
: service.assocPrice
);
}
}
public decimal VAT { get; set; } = 10.00M;
public bool AddVAT { get; set; } = false;
public decimal GetSelectedServicesExtras()
{
return services.Aggregate(0.0M, (sum, elm) =>
sum += elm.Key.Checked ? elm.Value() : 0M);
}
}
Now, to initialize this class, you can add your CheckBoxes and the associated value (here I'm using decimal
instead of int
because these are actually Currency values, so it's possible to add a VAT which will include decimals of course).
private ExtraServices extras = null;
private decimal extrasTotal = 0.0m;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
extras = new ExtraServices(new[] {
(chkAccess, 1.0m),
(chkPersonalTrainer, 20.0m),
(chkDietConsult, 20.0m),
(chkFitnessVid, 2.0m),
}) { AddVAT = false };
// Sets the initial values to 0.0m and formats the output
CalculateExtras();
}
The naming convention of the CheckBoxes is slightly different here: I, personally, prefer to prefix the name with the type, as in chkAccess
. I find this kind of organization simpler to handle. Just my preference.
All the CheckBoxes use the same CheckedChanged
event handler.
When the event is raised by one of the Controls, the total value of the extra services is calculated, stored in a Field (extrasTotal
) and a TextBox is updated, formatting the value to a Currency representation.
The extra CheckBox, chkAddVAT
, is used to show that the VAT calculation can be turned On/Off at any time. A similar method can be used for other associated Controls, extending the ExtraServices
class functionality to include the selection of all services and associated prices and automate the whole process.
private void chkExtras_CheckedChanged(object sender, EventArgs e)
{
CalculateExtras();
}
private void chkAddVAT_CheckedChanged(object sender, EventArgs e)
{
extras.AddVAT = (sender as CheckBox).Checked;
CalculateExtras();
}
private void CalculateExtras()
{
extrasTotal = extras.GetSelectedServicesExtras();
txtExtrasTotal.Text = extrasTotal.ToString("C");
}
decimal.ToString("C")
formats the decimal value using the CultureInfo.CurrentCulture
format. You can specify a different CultureInfo
, if needed:
e.g., decimal.ToString("C", CultureInfo.CreateSpecificCulture("en-GB"))
This is how it works:
Upvotes: 0
Reputation: 117027
The simplest thing to do here is to introduce an enumerable (list, array, etc) that captures your checkboxes and the extra cost:
var checks = new []
{
new { CheckBox = accessCheck, ExtraCost = 1, },
new { CheckBox = personalTrainerCheck, ExtraCost = 20, },
new { CheckBox = dietConsultCheck, ExtraCost = 20, },
new { CheckBox = fitnessVidCheck, ExtraCost = 2, },
};
Now it's easy to add up the extra costs:
var extraCost = 0;
foreach (var check in checks)
{
if (check.CheckBox.Checked)
{
extraCost += check.ExtraCost;
}
}
return extraCost;
Now, if you want to make it a little more streamlined, then you could replace this computation with this:
return checks.Aggregate(0, (a, x) => x.CheckBox.Checked ? x.ExtraCost : 0);
The next issue that comes up is that the condition become more complicated, like healthCheck.Checked && dietConsultCheck.Checked
or location == "CBD" && fitnessVidCheck.Checked
. You can then do this:
var checks = new (Func<bool> Condition, int ExtraCost)[]
{
(() => accessCheck.Checked, 1),
(() => personalTrainerCheck.Checked, 20),
(() => healthCheck.Checked && dietConsultCheck.Checked, 20),
(() => location == "CBD" && fitnessVidCheck.Checked, 1),
};
return checks.Aggregate(0, (a, x) => x.Condition() ? x.ExtraCost : 0);
Finally,
if you wanted to be able to change what gets checked you can build this up at run-time if you change from an array to a list and promote checks
to a field.
private List<(Func<bool> Condition, int ExtraCost)> _checks = null;
Then, at start up, you can set the defaults:
_checks = new List<(Func<bool> Condition, int ExtraCost)>()
{
(() => accessCheck.Checked, 1),
(() => personalTrainerCheck.Checked, 20),
(() => healthCheck.Checked && dietConsultCheck.Checked, 20),
(() => location == "CBD" && fitnessVidCheck.Checked, 1),
};
Now you're free to add more when you like:
_checks.Add((() => monthlySpecialCheck.Checked, -10));
Upvotes: 1