C # Каков наилучший способ определить тип унаследованного класса интерфейса? - PullRequest
1 голос
/ 24 декабря 2010

В моем приложении я работаю с критериями. У меня есть один базовый интерфейс Criteria и другие интерфейсы, которые наследуются от этого базового интерфейса:

               ICriteria
                  |
                  |
        ----------------------
         |                  |
    ITextCriteria        IChoices

Что я хотел бы знать, так это лучший способ узнать, что такое 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();

У меня вопрос, это лучший способ? Или есть лучший способ добиться этого?

Вероятность того, что появятся новые интерфейсы на основе ICriteria, велика.

EDIT:

У меня есть 2 типа элементов управления, которые я хочу динамически добавлять в свое приложение. Один элемент управления представляет собой текстовое поле, а другой - переключатель.

Для переключателя пользователь может определить параметры. Когда параметры определены, пользователь должен выбрать один из параметров, и выбранный параметр должен быть сохранен в базе данных (позже он используется для выполнения операций поиска). Таким образом, когда нажата кнопка «Сохранить», я должен определить выбранный тип (радио или текст) и сохранить возможности ответа (если это радио).

Для текстового поля у этого нет никаких вариантов ответа. По этой причине у него другой интерфейс.

Надеюсь, теперь я проясню это немного. Вот еще один вопрос, который связан с этим: C # Как реализовать интерфейс, если конкретные классы отличаются?

РЕДАКТИРОВАТЬ II:

Вот так выглядит мой метод SaveMultipleChoiceValues:

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);
    }
}

Ответы [ 4 ]

4 голосов
/ 24 декабря 2010

Это выглядит как главный пример полиморфизма.

Вместо того, чтобы пытаться сделать переключение типов в вашей реализации ICriteria, почему бы вам не добавить метод к ICriteria (или, возможно, виртуальный метод в некоторый общий базовый класс всех реализаций ICriteria), и просто позвони?

Очевидно, что для реализации этого метода потребуется доступ к объектам, которые не принадлежат вашим ICriteria экземплярам, ​​но эту проблему вы можете решить, используя другие шаблоны проектирования в соответствии со спецификой вашего сценария.

Обновление:

Вот полное решение, включающее отправленный вами код:

Создайте новый интерфейс ICriteriaView, который моделирует представление (в вашем случае Form), где отображаются ICriteria. Форма должна быть обработана в зависимости от конкретного интерфейса, который реализуют критерии, поэтому добавьте метод с одной перегрузкой для каждого интерфейса, существующего в вашем коде. Не добавляйте перегрузку для ICriteria. [1]

interface ICriteriaView {
    void ProcessCriteria(IChoices criteria);
    void ProcessCriteria(ITextCriteria criteria);
}

Ваша форма будет реализовывать этот интерфейс, предоставляя методы, при которых будет происходить подходящая обработка для каждого подтипа ICriteria:

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);
        }
    }

}

Каждая реализация ICriteria должна будет реализовать метод, который вызывает соответствующую перегрузку ICriteriaView для своего типа. Вот где происходит «магия перенаправления»: мы будем использовать полиморфизм, чтобы заставить компилятор «обнаружить» фактический тип ICriteria, в котором находится наш объект, а затем используем перегрузку метода на ICriteriaView.ProcessCriteria для доступа к соответствующему коду.

interface ICriteria {
    void PerformProcessingOn(ICriteriaView view);
}

interface IChoices : ICriteria {
}

interface ITextCriteria : ICriteria {
}

И здесь происходит отправка с соответствующей перегрузкой:

class MultipleChoice : IChoices {
    public PerformProcessingOn(ICriteriaView view) {
        view.ProcessCriteria(this);
    }
}

class SimpleInput : ITextCriteria {
    public PerformProcessingOn(ICriteriaView view) {
        view.ProcessCriteria(this);
    }
}

Тогда ваш код будет делать:

// 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();

Обслуживание:

Каждый раз, когда вы добавляете новую реализацию подинтерфейса ICriteria, определение ICriteria заставит вас реализовать метод PerformProcessingOn. Внутри этого метода все, что вы можете сделать - это вызвать view.ProcessCriteria(this). В свою очередь, это заставит вас реализовать соответствующую ProcessCriteria перегрузку в ICriteriaView и MyForm.

В результате мы достигли двух важных целей:

  1. Компилятор не позволит вам добавить новую реализацию ICriteria, не указав точно, как эта реализация должна взаимодействовать с ICriteriaView.
  2. Легко узнать из исходного кода точно , что MyView делает, например, с. IChoices при чтении кода для MultipleChoice. Структура кода приводит вас к MyForm.SaveMultipleChoiceValues «автоматически».

Примечания:

[1] Выбор добавления перегрузки для ICriteria сам по себе или нет - это действительно компромисс:

  • Если вы делаете добавляете один из них, то код такой:

    class MultipleChoice : IChoices {
        public PerformProcessingOn(ICriteriaView view) {
            view.ProcessCriteria(this);
        }
    }
    

    всегда будет успешно компилироваться, потому что даже если нет перегрузки ICriteriaView.ProcessCriteria(IChoices), все равно будет перегрузка ICriteriaView.ProcessCriteria(ICriteria), которую может использовать компилятор.

    Это означает, что при добавлении новой реализации субинтерфейса ICriteria компилятор больше не будет заставлять проверять, действительно ли реализация ICriteriaView.ProcessCriteria(ICriteria) работает правильно для вашего нового осуществление.

  • Если вы не добавляете его, то в момент написания view.ProcessCriteria(this); компилятор заставит проверить и обновить (*) ICriteriaView и MyForm соответственно.

В этом сценарии и с учетом предоставленной вами информации я считаю, что соответствующий выбор будет последним.

[2] Как вы можете видеть выше, реализация ICriteria.PerformProcessingOn внутри MultipleChoice и SimpleInput выглядит точно так же. Если эти два класса имеют общую базу (что на практике вполне возможно), у вас может возникнуть желание перенести «дублированный» код в эту базу. Не делай этого; это приведет к поломке решения.

Сложность в том, что внутри MultipleChoice, когда вы делаете view.ProcessCriteria(this);, компилятор может сделать вывод, что статический тип this равен IChoices - это то место, где происходит перенаправление!Если вы переместите вызов на ProcessCriteria внутри гипотетического базового класса CriteriaBase : ICriteria, тогда тип this станет ICriteria, и отправка вызова на соответствующую перегрузку ICriteriaView.ProcessCriteria больше не будет работать.

1 голос
/ 24 декабря 2010

Вы можете сделать это:

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;
    }
}

Но полиморфизм - это, вероятно, ваша лучшая ставка.

0 голосов
/ 24 декабря 2010

Вот долгосрочное решение постоянно расширяющегося списка критериев без добавления дополнительных if / then / else.

Хотя этот код сложен для кого-то, кто не привык к такому проектированию, он позволяет вам сохранить свой метод в соответствии с критериями и просто зарегистрировать новых делегатов для обработки дополнительных критериев.

Идея состоит в том, чтобы создать карту из Type объектов, содержащих делегаты для выполнения. Затем вы можете зарегистрировать новых делегатов для выполнения на основе новых Type s по мере их генерации.

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 голосов
/ 24 декабря 2010

Это не лучший способ. Если вы выполняете различные действия в зависимости от типа объекта, вам, вероятно, следует использовать вместо этого полиморфизм по множеству причин.

То, как вы используете полиморфизм, зависит от того, что вам действительно нужно сделать, основываясь на различных типах используемых критериев IC. Если вам просто нужно получить строку, содержащую всех их членов, вы можете легко добавить метод в ICriteria и передать ответственность самому классу вместо кода, который зависит от него. Это уменьшает дублирование, помещает код в логическое место и гарантирует, что вы не забудете добавить код для нового типа ICriteria.

Если вы дадите нам больше информации о том, как вы хотите, чтобы различные типы обрабатывались / вели себя, мы, вероятно, можем дать вам более конкретные советы. : D

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...