Разоблачение унаследованных событий - PullRequest
1 голос
/ 09 апреля 2019

Я пишу несколько пользовательских элементов управления для других программистов здесь.

Некоторые открытые EventHandlers с тем же именем, что и база, принимаются

(UserControl CodesCombo -> SelectedValueChanged)

некоторые не

(UserControl TextBox -> TextChanged)

Я пишу UserControl, который содержит TextBox. Мне нужно представить событие TextChanged потенциальным потребителям этого элемента управления.

У меня есть аналогичный элемент управления, основанный на ComboBox, и в этом я использовал

public event EventHandler SelectedValueChanged 
    {
    add { cbMain.SelectedValueChanged += value; }
    remove { cbMain.SelectedValueChanged -= value; }
    }         

чтобы выставить событие «Изменить» «SelectedValueChanged», и оно работает без проблем.

Однако, когда я пытаюсь использовать эту технику в моем элементе управления на основе TextBox аналогичным образом

public event EventHandler TextChanged
    {
    add { tbMain.TextChanged += value; }
    remove { tbMain.TextChanged -= value; }
    }

Я получаю предупреждение:

'MyTextBox.TextChanged' hides inherited member 'UserControl.TextChanged'. Use new keyword if hiding was intentional.

Я не совсем уверен, что означает сообщение, кроме очевидного, но я знаю, что не думаю, что хочу что-то скрыть. У меня есть внутренние функции SelectedValueChanged и TextChanged (cbMain_SelectedValueChanged, tbMain_TextChanged), которые выполняют несколько необходимых мне задач, но я также хочу позволить потребителю получать вызов Event для изменения текста, как и в ComboBox.

Кроме того, я не получаю никакого события «изменения» в доступном списке событий в тестовой программе.

Я обошел это сейчас, выставив событие как "TextChange"

new public event EventHandler TextChange
    {
    add { tbMain.TextChanged += value; }
    remove { tbMain.TextChanged -= value; }
    }

, который дает мне событие в списке и, кажется, работает достаточно хорошо, но я бы предпочел иметь общее решение для этого, поскольку мы делаем еще несколько элементов управления для нашего пакета, и я не думаю, что Я могу избежать неприятностей с именами, которые просто «выключены».

Есть идеи, что на самом деле говорит мне это сообщение, и как я могу получить свое событие внутренне, а также получить его пользователь?

Спасибо!

Обновление: запрашивается более конкретный код:

namespace NCFLSToolbox
    {
    public partial class NCFLSCodesCombo : UserControl
        {
        //Listed in the Events for System.Windows.Forms.ComboBox
        private void cbMain_SelectedValueChanged(object sender, EventArgs e)
            {
            ControlRequiredColoring();
            }

        //Exposed Event for user
        public event EventHandler SelectedValueChanged 
            {
            add { cbMain.SelectedValueChanged += value; }
            remove { cbMain.SelectedValueChanged -= value; }
            }
        }


    public partial class NCFLSTextbox : UserControl
        {
        //Listed in the Events for System.Windows.Forms.TextBox
        private void tbMain_TextChanged(object sender, EventArgs e)
            {
            ControlRequiredColoring();
            }


        //Couldn't expose "TextChanged" by name...
        ////public event EventHandler TextChanged
        ////    {
        ////    add { tbMain.TextChanged += value; }
        ////    remove { tbMain.TextChanged -= value; }
        ////    }

        //...so I exposed "TextChange" instead.
        public event EventHandler TextChange
            {
            add { tbMain.TextChanged += value; }
            remove { tbMain.TextChanged -= value; }
            }
        }
    }

Ответы [ 2 ]

5 голосов
/ 09 апреля 2019

Есть идеи, что на самом деле говорит мне это сообщение?

Прежде всего, поймите, что значит скрывать. Забудьте о том, что это событие на мгновение. Если у нас есть:

class B     { public void M() { Console.WriteLine("B.M()"); } }
class D : B { public void M() { Console.WriteLine("D.M()"); } }

тогда у нас есть два метода, которые называются М. Если вы скажете:

D d = new D();
B b = d;
d.M(); // D.M();
b.M(); // B.M();

Компилятор предупреждает вас, что у вас есть два метода, а не один, и тот, который вы получите, зависит от типа получателя во время компиляции, НЕ типа получателя во время выполнения.

Почему это, вероятно, не то, что вы хотите?

Три причины.

Во-первых, многие программисты на C # приходят на C # из Java, где методы автоматически переопределяются. В C # методы должны быть virtual для переопределения. Компилятор говорит вам, что вы не получаете семантику, которую вы могли ожидать.

Во-вторых, это должно смягчить проблему хрупкого базового класса. Предположим, что автор B предоставляет команде D сборку DLL, содержащую B без M, и D наследует ее, добавляя метод D.M. Затем команда B понимает, что может реализовать M, поэтому они добавляют его в B и дают команде D новую сборку. Теперь команду D следует предупредить, что B.M существует, чтобы они могли решить, удалять ли D.M или нет.

В-третьих, почему вы делаете это в первую очередь? Если в базовом классе уже есть событие, которое вы хотите, просто используйте его! Не создавайте новый, который использует старый; просто используйте старый. Компилятор говорит вам, что вы определенно делаете что-то странное и, вероятно, неправильно.

Если вы намерены иметь двух членов в двух разных классах, где один скрывает другой, вы говорите компилятору: «Я думал об этом, и это намеренно», добавив new к члену. То есть «это новый участник, и я хотел сделать его новым, а не переопределять старый».

1 голос
/ 11 апреля 2019

Проблема в том, что события TextBox не распространяются на родительский пользовательский элемент управления. Если вы хотите, чтобы они это сделали, есть два способа сделать это:

1) Пусть событие на пользовательском элементе управления переправляет все добавления / удаления в обработчик событий в обработчик событий UserControl. Это примерно то, что ваш вопрос пытается сделать. Однако я не рекомендую это.

2) Имейте событие в событиях запуска TextBox на родительском пользовательском элементе управления.

Итак, просто запустите этот код в вашем конструкторе ниже InitializeComponent():
tbMain.TextChanged += (sender,e)=>OnTextChanged(e);

При таком подходе событие TextChanged в TextBox вызовет OnTextChanged, что вызывает событие TextChanged. Вы не можете вызывать события базового класса напрямую (следовательно, почему предоставляется метод OnTextChanged).

Редактировать: tbMain.TextChanged += (sender,e)=>OnTextChanged(e); семантически эквивалентно приведенному ниже коду:

tbMain.TextChanged += OnTbMainTextChanged;
...
} //End of Constructor

private void OnTbMainTextChanged(object sender, EventArgs e)
{
    OnTextChanged(e);
}

Преимущество использования лямбда-функции заключается в том, что она более автономна. Среди других преимуществ, использование автономной лямбда-функции делает очевидным для будущих разработчиков кода, что sender не распространяется, без необходимости, чтобы сопровождающий переходил к названному методу. Это то, что вы хотите: от точки зрения подписчиков до пользовательского элемента управления использование TextBox для реализации вашего элемента управления - это деталь реализации.

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