In my application I work with criterias. I have one base Criteria interface and and other interfaces who inherits from this base interface:
ICriteria | | ---------------------- | | ITextCriteria IChoices
What I'd like to know is, what is the best way to know what Type the class is?
In my code I have a dropdown box and based on that I have to determine the type:
// Get selected criteria
var selectedCriteria = cmbType.SelectedItem as ICriteria;
if (selectedCriteria is IChoices)
{
//selectedCriteria = cmbType.SelectedItem as IChoices; Doesn't work
IChoices criteria = selectedCriteria as IChoices;//cmbType.SelectedItem as IChoices;
SaveMultipleChoiceValues(criteria);
//_category.AddCriteria(criteria);
}
else
{
//ICriteria criteria = selectedCriteria; //cmbType.SelectedItem as ICriteria;
if (selectedCriteria.GetCriteriaType() == CriteriaTypes.None)
{
return;
}
//_category.AddCriteria(criteria);
}
_category.AddCriteria(selectedCriteria);
selectedCriteria.LabelText = txtLabeltext.Text;
this.Close();
My question is, is this the best way? Or is there a better way to achieve this?
The chance is big that there are coming more interfaces based on ICriteria.
EDIT:
I have 2 types of controls which I want to add dynamically to my application. One control is a textbox and the other is a radio button.
For a radio button the user can define the options. When the options are defined, the user must choose one of the options and the chosen option must be saved in the database (this is later used to perform search operations). So, when the Save button is clicked, I have to determine the chosen type (radio or text) and save the answer possibilities (if it is a radio).
For a textbox, this doesn't have any answer possibilities. For that reason it has a different interface.
I hope I make it a little bit clearer now. Here is another question which is related: C# How to implement interface where concrete classes differs?
EDIT II:
This is how my method SaveMultipleChoiceValues
looks like:
private void SaveMultipleChoiceValues(IChoices criteria)
{
foreach (DataGridViewRow row in dgvCriteriaControls.Rows)
{
if (row == dgvCriteriaControls.Rows[dgvCriteriaControls.Rows.Count - 1])
continue;
//multipleChoice.AddChoice(row.Cells["Name"].Value.ToString());
string choice = row.Cells["Name"].Value.ToString();
criteria.AddChoice(choice);
}
}
This looks like a prime example for polymorphism.
Instead of trying to do a type switch on your ICriteria
implementation, why don't you add a method to ICriteria
(or possibly a virtual method to some common base class of all ICriteria
implementations), and just call that?
Obviously the implementation of this method would need access to objects that do not belong in your ICriteria
instances, but that is a problem you can solve using other design patterns according to the specifics of your scenario.
Update:
Here's a complete solution, incorporating the code you posted:
Create a new interface ICriteriaView
which models the view (in your case a Form
) where ICriteria
are displayed. The form needs to do some processing depending on the exact interface that criteria implement, so add a method with one overload for each interface that exists in your code. Do not add an overload for ICriteria
itself. [1]
interface ICriteriaView {
void ProcessCriteria(IChoices criteria);
void ProcessCriteria(ITextCriteria criteria);
}
Your form will implement this interface, providing methods where suitable processing for each subtype of ICriteria
will occur:
class MyForm : ICriteriaView {
public void ProcessCriteria(IChoices criteria) {
this.SaveMultipleChoiceValues(criteria);
}
public void ProcessCriteria(ITextCriteria criteria) {
// do nothing
}
private void SaveMultipleChoiceValues(IChoices criteria)
{
foreach (DataGridViewRow row in dgvCriteriaControls.Rows)
{
if (row == dgvCriteriaControls.Rows[dgvCriteriaControls.Rows.Count - 1])
continue;
//multipleChoice.AddChoice(row.Cells["Name"].Value.ToString());
string choice = row.Cells["Name"].Value.ToString();
criteria.AddChoice(choice);
}
}
}
Each implementation of ICriteria
will need to implement a method which calls the appropriate ICriteriaView
overload for its type. This is where the "redirection magic" happens: we will use polymorphism to get the compiler to "discover" the actual type of ICriteria
our object is, and then use method overloading on ICriteriaView.ProcessCriteria
to access the appropriate code.
interface ICriteria {
void PerformProcessingOn(ICriteriaView view);
}
interface IChoices : ICriteria {
}
interface ITextCriteria : ICriteria {
}
And this is where the dispatch to the appropriate overload happens:
class MultipleChoice : IChoices {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
class SimpleInput : ITextCriteria {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
Then, your code would do:
// Get selected criteria
var selectedCriteria = cmbType.SelectedItem as ICriteria;
// Here's where polymorphism kicks in
selectedCriteria.PerformProcessingOn(this);
// Finally, code that runs the same for all objects
_category.AddCriteria(selectedCriteria);
selectedCriteria.LabelText = txtLabeltext.Text;
this.Close();
Maintenance:
Whenever you add a new ICriteria
sub-interface implementation, the definition of ICriteria
will force you to implement the PerformProcessingOn
method on it. Inside that method, all you can do really is call view.ProcessCriteria(this)
. In turn, this will force you to implement an appropriate ProcessCriteria
overload in ICriteriaView
and MyForm
.
As a result, we have achieved two important objectives:
- The compiler will not allow you to add a new
ICriteria
implementation without specifying exactly how that implementation should interact withICriteriaView
. - It is easy to discover from source code exactly what
MyView
does with e.g.IChoices
when reading the code forMultipleChoice
. The structure of the code leads you toMyForm.SaveMultipleChoiceValues
"automatically".
Notes:
[1] The choice of adding an overload for ICriteria
itself or not is really a tradeoff:
If you do add one, then code like this:
class MultipleChoice : IChoices { public PerformProcessingOn(ICriteriaView view) { view.ProcessCriteria(this); } }
will compile successfully always, because even if there is no
ICriteriaView.ProcessCriteria(IChoices)
overload there will still be theICriteriaView.ProcessCriteria(ICriteria)
overload that the compiler can use.This means that, when adding a new
ICriteria
sub-interface implementation, the compiler will no longer force you to go check if the implementation ofICriteriaView.ProcessCriteria(ICriteria)
really does the right thing for your new implementation.If you do not add one, then the moment you write
view.ProcessCriteria(this);
the compiler will force you to go check (and update)ICriteriaView
andMyForm
accordingly.
In this scenario, and with the information you have provided, I believe that the appropriate choice would be the last one.
[2] As you can see above, the implementation of ICriteria.PerformProcessingOn
inside MultipleChoice
and SimpleInput
looks exactly the same. If these two classes have a common base (which is quite possible in practice), you might be tempted to move the "duplicated" code into that base. Do not do that; it will cause the solution to break.
The tricky part is that inside MultipleChoice
, when you do view.ProcessCriteria(this);
the compiler can infer that the static type of this
is IChoices
-- this is where the redirection happens! If you move the call to ProcessCriteria
inside a hypothetical base class CriteriaBase : ICriteria
, then the type of this
will become ICriteria
and the dispatch of the call to the appropriate ICriteriaView.ProcessCriteria
overload will no longer work.