Преимущества использования делегатов?

Я хочу реализовать шаблон Observer на VB.NET, C# или другом первоклассном языке .NET. Я слышал, что для этого можно использовать делегатов, но не могу понять, почему они предпочтительнее простых старых интерфейсов, реализованных на наблюдателях. Так,

  • Почему я должен использовать делегаты вместо определения моих собственных интерфейсов и передачи ссылок на объекты, реализующие их?
  • Почему я могу отказаться от использования делегатов и использовать старые добрые интерфейсы?

Вы спрашиваете об обосновании функционального программирования или зачем нужен тип «делегат» в C# для функционального программирования?

Karmastan 28.06.2010 21:24

Можете ли вы привести пример ситуации, когда у вас есть это сомнение? Я не уверен, что понимаю, о чем вы просите.

Gabe 28.06.2010 21:25

возможный дубликат Делегаты - это не просто сокращенные интерфейсы?

nawfal 15.05.2013 12:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
3
12 241
14

Ответы 14

По сути, делегат передает ссылку на метод, а не на объект ... Интерфейс - это ссылка на подмножество методов, реализованных объектом ...

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

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

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

Предпочтительно, чтобы источник события никогда не знал о прослушивателях событий, а прослушиватель событий никогда не должен заботиться о том, кто инициировал событие. Сегодня все обстоит иначе, потому что в прослушивателе событий вы обычно получаете исходный объект события.

С учетом сказанного делегаты - идеальный инструмент для этой работы. Они позволяют разделить источник событий и наблюдатель событий, поскольку источнику событий не нужно хранить список всех объектов-наблюдателей. Он только хранит список «указателей на функции» (делегатов) наблюдателей. Поэтому я считаю это большим преимуществом перед интерфейсами.

Посмотри на это с другой стороны. Какое преимущество будет иметь пользовательский интерфейс по сравнению со стандартным способом, который поддерживается языком как в синтаксисе, так и в библиотеке?

Конечно, существуют случаи являются, когда это индивидуальное решение может иметь преимущества, и в таких случаях вы должны его использовать. Во всех остальных случаях используйте наиболее каноническое решение. Это меньше работы, более интуитивно понятно (потому что этого ожидают пользователи), имеет большую поддержку со стороны инструментов (включая IDE), и, скорее всего, компилятор обрабатывает их по-другому, что приводит к более эффективному коду.

Не изобретайте велосипед (если текущая версия не работает).

У метода "интерфейса наблюдателя" есть несколько преимуществ, которые я вижу: -1- при слабой ссылке на наблюдателя можно выполнить на нем его метод "наблюдения"; в .net 2.x нет средств для сохранения делегата без сохранения сильной ссылки (или «воссоздания» слабой ссылки в делегат). -2- С шаблоном «наблюдатель / интерфейс» можно спросить наблюдателей, «интересуются» ли они по-прежнему, и удалить их, если нет. При использовании делегатов такое удаление намного сложнее и требует большей координации между издателем событий и подписчиком.

supercat 28.06.2010 22:56

@supercat: ваша точка 1 верна лишь частично - вы можете реализовать свои собственные классы, подобные делегатам, для использования со слабыми ссылками - это особенно важно для событий и поддерживается синтаксисом custom event. Я не уверен в вашем втором пункте - разве это не то, что наблюдатель может решить в любом случае, как только он будет вызван? Я не вижу проблемы.

Konrad Rudolph 29.06.2010 00:23

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

supercat 14.12.2010 09:08

Есть два места, где вы можете использовать делегатов в шаблоне наблюдателя. Поскольку я не уверен, о каком из них вы имеете в виду, я постараюсь ответить на оба вопроса.

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

private delegate void UpdateHandler(string message);
private UpdateHandler Update;

public void Register(IObserver observer)
{
    Update+=observer.Update;
}

public void Unregister(IObserver observer)
{
    Update-=observer.Update;
}

public void Notify(string message)
{
    Update(message);
}

вместо

public Subject()
{
    observers = new List<IObserver>();
}

public void Register(IObserver observer)
{
    observers.Add(observer);
}

public void Unregister(IObserver observer)
{
    observers.Remove(observer);
}

public void Notify(string message)
{
    // call update method for every observer
    foreach (IObserver observer in observers)
    {
        observer.Update(message);
    }
}

Если вам не нужно делать что-то особенное и не требуется ссылка на весь объект IObserver, я думаю, что делегаты будут чище.

Во втором случае вместо IObervers, например, используются проходные делегаты.

public delegate void UpdateHandler(string message);
private UpdateHandler Update;

public void Register(UpdateHandler observerRoutine)
{
    Update+=observerRoutine;
}

public void Unregister(UpdateHandler observerRoutine)
{
    Update-=observerRoutine;
}

public void Notify(string message)
{
    Update(message);
}

При этом наблюдателям не нужно реализовывать интерфейс. Вы даже можете передать лямбда-выражение. Вот в чем разница в уровне контроля. Хорошо это или плохо - решать вам.

Технически, вам не нужно использовать делегаты (кроме случаев использования обработчики событий, тогда это обязательно). Вы можете обойтись без них. На самом деле, это просто еще один инструмент в ящике для инструментов.

Первое, что приходит на ум при их использовании, - это Инверсия контроля. Каждый раз, когда вы хотите контролировать поведение функции извне, самый простой способ сделать это - поместить делегат в качестве параметра и заставить его выполнять делегат.

Я использовал вариации паттерна стратегии, которые довольно хорошо работали с делегатами стратегии. Или модель издатель / подписчик, где я предоставляю класс подписчика, который реализует 90 +% типичных функций подписчика в качестве оболочки для обратного вызова для обработки остальных 10%.

Toby 28.06.2010 21:42

Вы официант, а я заявка. Я хочу сказать вам, чтобы вы вызывали один из моих методов, когда обнаруживаете, что что-то происходит. Для этого я передаю вам делегата моего метода, который я хочу, чтобы вы вызвали. Я сам не называю этот метод, потому что хочу, чтобы вы вызывали его, когда вы что-то обнаруживаете. Вы не вызываете мой метод напрямую, потому что вы не знаете (во время компиляции), что метод существует (меня даже не писали, когда вы были созданы); вместо этого вы вызываете любой метод, указанный делегатом, который вы получаете во время выполнения.

да, теперь я вас немного понимаю, но не могли бы вы предоставить какой-нибудь пример кода, который будет лучше для меня, чтобы понять

Cloud2010 28.06.2010 21:36

@ Cloud2010 Например, просмотрите все места, где используются делегаты и / или события в структуре> NET: например, Событие Control.Click или List <T> .ForEach Метод.

ChrisW 28.06.2010 21:47

@ Cloud2010: Вот пример функции atexit (и ссылка на on_exit), которая выполняется при выходе из процесса. Функция bye (в примере) вызывается ОС, когда она обнаруживает, что процесс завершается. gnu.org/s/libc/manual/html_node/Cleanups-on-Exit.html

FrustratedWithFormsDesigner 28.06.2010 21:48

Я повторяю ответ, который дал этот вопрос.

Мне всегда нравилась метафора радиостанции.

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

Без этого механизма регистрации (или события). Радиостанция должна будет связаться со всеми радиостанциями по очереди и спросить, хочет ли она трансляцию, если ваше радио ответит «да», а затем послать на нее сигнал напрямую.

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

Это скорее аналогия с События, а не с делегаты в целом.

Adam Robinson 28.06.2010 21:33

Думаю, я делаю прыжок или два, но делегат - это просто ссылка на метод. OP спросил, зачем вам нужна ссылка на метод. Я ответил метафорой классического использования делегатов (т.е. событий).

Matthew Vines 28.06.2010 21:58

Когда вы можете напрямую вызвать метод, вам не нужен делегат.

Делегат полезен, когда код, вызывающий метод, не знает / не заботится о том, какой метод он вызывает является - например, вы можете вызвать длительную задачу и передать ей делегат методу обратного вызова, который задача может использовать для отправлять уведомления о своем статусе.

Вот (очень глупый) пример кода:

enum TaskStatus
{
   Started,
   StillProcessing,
   Finished
}

delegate void CallbackDelegate(Task t, TaskStatus status);

class Task
{
    public void Start(CallbackDelegate callback)
    {
        callback(this, TaskStatus.Started);

        // calculate PI to 1 billion digits
        for (...)
        {
            callback(this, TaskStatus.StillProcessing);
        }

        callback(this, TaskStatus.Finished);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Task t = new Task();
        t.Start(new CallbackDelegate(MyCallbackMethod));
    }

    static void MyCallbackMethod(Task t, TaskStatus status)
    {
        Console.WriteLine("The task status is {0}", status);
    }
}

Как видите, класс Task не знает и не заботится о том, что - в данном случае - делегат относится к методу, который выводит статус задачи на консоль. Этот метод также может отправлять статус по сетевому соединению на другой компьютер. И т.п.

Делегаты - это строгая типизация интерфейсов функций / методов.

Если ваш язык придерживается позиции, что должна быть строгая типизация и что у него есть функции первого класса (обе из которых есть в C#), то это будет несовместимо с тем, что нет имеет делегатов.

Рассмотрим любой метод, который принимает делегат. Если бы у вас не было делегата, как бы вы ему что-то передали? И как у вызываемого могут быть какие-либо гарантии относительно его типа?

Вы не думаете как программист.

Вопрос в том, зачем вам вызывать функцию напрямую когда вы могли вызвать делегату?

A famous aphorism of David Wheeler goes: All problems in computer science can be solved by another level of indirection.

Я немного иронизирую. Очевидно, что большую часть времени вы будете вызывать функции напрямую, особенно внутри модуля. Но делегаты полезны, когда функция должна быть вызвана в контексте, где содержащий объект недоступен (или уместен), например обратные вызовы событий.

en.wikipedia.org/wiki/Indirection говорит, что это часто намеренно неправильно цитируется, когда «уровень абстракции» заменяется на «уровень косвенности». Следствие этого Кевлина Хенни звучит так: «... кроме проблемы слишком большого количества уровней косвенности».
ChrisW 28.06.2010 21:30

На самом деле между Sun и Microsoft произошел интересный спор по поводу делегатов. Хотя Sun заняла довольно жесткую позицию против делегатов, я считаю, что Microsoft сделала еще более решительный аргумент в пользу использования делегатов. Вот сообщения:

http://java.sun.com/docs/white/delegates.html

http://msdn.microsoft.com/en-us/vjsharp/bb188664.aspx

Думаю, вы найдете это чтение интересным ...

Вау, это ссылки из учебников истории. Я нахожу большинство вопросов, поднятых с обеих сторон, довольно тривиальными, и просто из-за неприязни между двумя сторонами по поводу того, что Microsoft затем меняет язык. Это в основном сводится к тому, стоит ли сохранение одной строки кода (сигнатуры метода) добавления делегатов. На мой взгляд, нет веских причин для их размещения.

CurtainDog 30.06.2010 16:33

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

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

Каждый приведенный здесь пример хорош, где вы можете реализовать их, как кто-то сказал, это просто еще одна функция на языке, с которым вы можете играть.

Привет

Вот кое-что, что я могу описать как причину использования делегата. Следующий код написан на C#. Следите за комментариями.

public delegate string TestDelegate();
protected void Page_Load(object sender, EventArgs e)
{
    TestDelegate TD1 = new TestDelegate(DiaplayMethodD1);
    TestDelegate TD2 = new TestDelegate(DiaplayMethodD2);
    TD2 = TD1 + TD2; // Make TD2 as multi-cast delegate
    lblDisplay.Text =  TD1(); // invoke delegate
    lblAnotherDisplay.Text = TD2();


    // Note: Using a delegate allows the programmer to encapsulate a reference 
    //       to a method inside a delegate object. Its like the function pointer
    //       in C or C++.    
}
//the Signature has to be same.
public string DiaplayMethodD1()
{
    //lblDisplay.Text = "Multi-Cast Delegate on EXECUTION"; // Enable on multi-cast 
    return "This is returned from the first method of delegate explanation";
}
// The Method can be static also
public static string DiaplayMethodD2()
{
    return " Extra words from second method";
}

Наилучшие пожелания, Притом Нанди, Бангладеш

Вот пример, который может помочь.

Есть приложение, использующее большой набор данных. Необходима функция, позволяющая фильтровать данные. Можно указать 6 различных фильтров.

Непосредственная мысль состоит в том, чтобы создать 6 различных методов, каждый из которых будет возвращать отфильтрованные данные. Например

public Data FilterByAge (int age)

общедоступные данные FilterBySize (размер int)

.... и так далее.

Это нормально, но очень ограничено и создает мусор, потому что он закрыт для расширения.

Лучше всего использовать один метод Filter и передавать информацию о том, как данные должны быть отфильтрованы. Здесь можно использовать делегата. Делегат - это функция, которую можно применить к данным для их фильтрации.

общедоступный фильтр данных (фильтр действий)

тогда код для использования становится

Фильтр (data => data.age> 30);

Фильтр (data => data.size = 19);

Данные кода => бла-бла становится делегатом. Код становится более гибким и остается открытым.

Другие вопросы по теме