开发者

C# What is the best way to determine the type of an inherited interface class?

开发者 https://www.devze.com 2023-01-31 22:21 出处:网络
In my application I work with criterias. I have one base Criteria interface and and other interfaces who inherits from this base interface:

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 SaveMultipleChoiceVa开发者_运维技巧lues(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:

  1. The compiler will not allow you to add a new ICriteria implementation without specifying exactly how that implementation should interact with ICriteriaView.
  2. It is easy to discover from source code exactly what MyView does with e.g. IChoices when reading the code for MultipleChoice. The structure of the code leads you to MyForm.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 the ICriteriaView.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 of ICriteriaView.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 and MyForm 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.


You could do this:

var selectedCriteria = cmbType.SelectedItem as ICriteria;
if (typeof(IChoices).IsAssignableFrom(selectedCriteria.GetType()))
{
    IChoices criteria = selectedCriteria as IChoices;
    SaveMultipleChoiceValues(criteria);
}
else if(typeof(ITextCriteria).IsAssignableFrom(selectedCriteria.GetType()))
{
    if (selectedCriteria.GetCriteriaType() == CriteriaTypes.None)
    {
        return;
    }
}

But polymorphism is probably your best bet.


That is not the best way. If you are performing different actions based on the type of an object, you should probably be using polymorphism instead for a myriad number of reasons.

How you use polymorphism depends on what you actually need to have done based on the different types of ICriteria that are being used. If you just need to get a string containing all of their members, you could easily add a method to ICriteria and hand the responsibility to the class itself instead of the code that depends on it. This reduces duplication, puts code in a logical place, and makes sure you don't forget to add code for a new type of ICriteria.

If you give us more information on how you want different types to be treated/behave, we can probably give you more specific advice. :D


Here is a long term solution to an ever expanding list of critera without having to add more if/then/else.

While this code is complex to someone not used to designing in this manner, it allows you to keep your method dealing with criteria the same, and just register new delegates to handle additional criteria.

The idea is to create a map of Type objects that hold delegates in which to execute. You can then register new delegates to execute based on new Types as you generate them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Stackoverflow_4527626
{
    delegate void CriteraDelegate(params object[] args);

    class CriteraManager
    {
        private Dictionary<Type, CriteraDelegate> criterian = new Dictionary<Type, CriteraDelegate>();

        public void RegisterCritera(Type type, CriteraDelegate del)
        {
            criterian[type] = del;
        }

        public void Execute(Object criteria, params object[] args)
        {
            Type type = criteria.GetType();

            /// Check to see if the specific type
            /// is in the list. 
            if (criterian.ContainsKey(type))
            {
                criterian[type](args);
            }
            /// If it isn't perform a more exhaustive search for
            /// any sub types.
            else
            {
                foreach (Type keyType in criterian.Keys)
                {
                    if (keyType.IsAssignableFrom(type))
                    {
                        criterian[keyType](args);
                        return;
                    }
                }

                throw new ArgumentException("A delegate for Type " + type + " does not exist.");
            }
        }
    }


    interface InterfaceA { }
    interface InterfaceB1 : InterfaceA { }
    interface InterfaceB2 : InterfaceA { }
    interface InterfaceC { }
    class ClassB1 : InterfaceB1 { }
    class ClassB2 : InterfaceB2 { }
    class ClassC : InterfaceC { }

    class Program
    {
        static void ExecuteCritera1(params object[] args)
        {
            Console.WriteLine("ExecuteCritera1:");
            foreach (object arg in args)
                Console.WriteLine(arg);
        }

        static void ExecuteCritera2(params object[] args)
        {
            Console.WriteLine("ExecuteCritera2:");
            foreach (object arg in args)
                Console.WriteLine(arg);
        }

        static void Main(string[] args)
        {
            CriteraDelegate exampleDelegate1 = new CriteraDelegate(ExecuteCritera1);
            CriteraDelegate exampleDelegate2 = new CriteraDelegate(ExecuteCritera2);

            CriteraManager manager = new CriteraManager();
            manager.RegisterCritera(typeof(InterfaceB1), exampleDelegate2);
            manager.RegisterCritera(typeof(InterfaceB2), exampleDelegate2);
            manager.RegisterCritera(typeof(InterfaceC), exampleDelegate1);

            ClassB1 b1 = new ClassB1();
            ClassB2 b2 = new ClassB2();
            ClassC c = new ClassC();

            manager.Execute(b1, "Should execute delegate 2");
            manager.Execute(b2, "Should execute delegate 2");
            manager.Execute(c, "Should execute delegate 1");
        }
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消