Являются ли делегаты не просто сокращенными интерфейсами?

Предположим, у нас есть:

interface Foo 
{
 bool Func(int x);
}

class Bar: Foo
{
  bool Func(int x)
  {
   return (x>0);
  }  
}

class Baz: Foo
{
  bool Func(int x)
  {
   return (x<0);
  }  
}

Теперь мы можем перебрасывать Bar и Baz как Foos и вызывать их методы Func.

Делегаты немного упрощают это:

delegate bool Foo(int x);

bool Bar(int x)
{
 return (x<0);
}

bool Baz(int x)
{
 return (x>0);
}

Теперь мы можем использовать Bar и Baz в качестве делегатов Foo.

В чем реальная польза от делегатов, кроме сокращения кода?


person Michiel Borkent    schedule 18.09.2008    source источник


Ответы (10)


Есть небольшая разница: делегаты могут получить доступ к переменным-членам классов, в которых они определены. В C# (в отличие от Java) все внутренние классы считаются статическими. Поэтому, если вы используете интерфейс для управления обратным вызовом, например. ActionListener для кнопки. Реализующему внутреннему классу необходимо передать (через конструктор) ссылки на части содержащего класса, с которыми ему может потребоваться взаимодействовать во время обратного вызова. У делегатов нет этого ограничения, поэтому уменьшается объем кода, необходимого для реализации обратного вызова.

Более короткий и лаконичный код также является достойным преимуществом.

person Michael Barker    schedule 18.09.2008
comment
Интересно отметить, что делегаты могут получать доступ к членам. Передаваемый метод-оболочка класса может легко действовать как курьер для результирующих данных, но прямой доступ — гораздо более быстрый способ сделать это. - person Rick Minerich; 18.09.2008
comment
Это не просто переменные-члены. Это даже локальные переменные или параметры метода, в котором определен анонимный делегат. - person Daniel Earwicker; 05.03.2009
comment
@DanielEarwicker: подпрограмма, в которой определено замыкание, определяет специальный скрытый класс для хранения локальных переменных, создает экземпляр такого класса каждый раз, когда эти переменные входят в область видимости, и использует поля этого класса вместо обычных локальных переменных. Сценарии, использующие замыкания, на самом деле лучше работают с интерфейсами, чем с делегатами. - person supercat; 14.02.2012
comment
@supercat Верно. Но как вы думаете, почему интерфейсы будут работать лучше, чем делегаты? Делегат — это логически то же самое, что и интерфейс с одним методом. - person Daniel Earwicker; 15.02.2012
comment
@DanielEarwicker: По крайней мере две причины: (1) Предположим, что подпрограмма вызовет Action в какой-то ситуации, и в этой ситуации я хочу, чтобы она вызывала Foo(x) с текущим значением локальной переменной x. Передача такого запроса в качестве делегата требует создания двух экземпляров кучи: временного объекта класса для хранения x и делегата для его вызова. Передача запроса в качестве типа интерфейса потребует создания одного экземпляра кучи (реализации интерфейса). Передача его в качестве универсального, ограниченного интерфейсом, может не потребовать его создания. - person supercat; 15.02.2012
comment
(2) Интерфейсы могут иметь открытые универсальные методы, тогда как делегаты не могут. Предположим, у меня есть коллекция объектов, не имеющих общего базового типа, но все они имеют типы, ограниченные реализацией IFoo и IBar. Я хотел бы иметь возможность запускать внешнюю процедуру, которая принимает параметр, ограниченный IFoo и IBar. Невозможно указать, что делегат принимает параметр типа T:IFoo,IBar, но можно объявить метод интерфейса, который делает это. - person supercat; 15.02.2012
comment
(1) экземпляры кучи не особенно дороги в CLR. См. smellegantcode.wordpress. com/2009/03/01/ (2) Что не так с delegate void MyDelegate<T>(T v) where T : IFoo, IBar; ? - person Daniel Earwicker; 15.02.2012
comment
@DanielEarwicker: проблема в том, что подпрограмма, создающая делегата, должна указывать тип T. Предположим, что у класса есть набор объектов, все из которых реализуют I1 и I2, но у них нет общего базового типа, реализующего оба; он хочет раскрыть метод ForAll. Если бы такой метод принимал объект типа interface IActUponConstrained<I1,I2>{void Act<T>(T param) where T:I1,I2;}, он мог бы вызывать Act<T> для каждого элемента без того, чтобы сущность, создавшая IActUponConstrained<I1,I2>, знала типы отдельных элементов. - person supercat; 20.07.2012
comment
@DanielEarwicker: Если кто-то не хочет использовать Reflection, необходим универсальный интерфейс, потому что общие параметры ввода могут перемещаться только вниз (вглубь) стека вызовов. Коллекция абстрактного класса ThingImplementing<I1,I2> может содержать экземпляры производных классов ThingImplementing<I1,I2>.WithType<ActualType> where ActualType:I1,I2. Каждый экземпляр привязан к универсальному типу, с которым он был создан, но если другой код будет вызываться с использованием этого параметра типа, экземпляру придется выполнять вызов самому. Если Foo и Bar не связаны, но реализуют... - person supercat; 20.07.2012
comment
... оба интерфейса, невозможно определить делегат с параметром типа, ограниченным как I1, так и I2, которому ThingImplementing<I1,I2>.WithType<Foo> может передать Foo, и которому ThingImplementing<I1,I2>.WithType<Bar> может передать Bar. Однако можно было бы определить реализацию IActUponConstrained<I1,I2>, передать ее обоим объектам и иметь первый вызов Act<Foo>(Foo param), а второй вызов Act<Bar>(Bar param). - person supercat; 20.07.2012
comment
Ах, вы имеете в виду вот так? smellegantcode.wordpress .com/2010/04/17/ - person Daniel Earwicker; 21.07.2012
comment
@DanielEarwicker: Точно. Возможность предоставить потребителю типа интерфейса параметр универсального типа не похожа ни на что, что можно сделать с делегатами .net. На самом деле, я не уверен, что в фреймворке была бы реальная потребность в делегатах, если бы компиляторы могли делать для универсальных интерфейсов некоторые вещи, которые они делают для делегатов (например, учитывая интерфейс с методом Invoke, имеющим определенную сигнатуру, конструкцию класс, конструктор которого принимает две реализации этого интерфейса и который реализует invoke, вызывая оба экземпляра). Конечно... - person supercat; 25.07.2012
comment
... дженериков не существовало в .net 1.0, поэтому квазиуниверсальные делегаты были необходимой временной мерой. Между прочим, мне интересно, почему сгенерированные компилятором производные от Delegate не определяют свои собственные специфичные для типа методы Combine и Remove? Такие методы могли бы правильно работать с контравариантными типами делегатов, тогда как Delegate.Combine не могут. - person supercat; 25.07.2012

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

Их также можно использовать во многом таким же образом: вместо передачи целого класса, содержащего нужный вам метод, вы можете передать только делегат. Это экономит много кода и создает гораздо более читаемый код.

Более того, с появлением лямбда-выражений их теперь также можно легко определить на лету, что является огромным преимуществом. Хотя на C# и ВОЗМОЖНО создавать классы «на лету», на самом деле это огромная головная боль.

Сравнение этих двух — интересная концепция. Раньше я не задумывался, насколько похожи идеи с точки зрения варианта использования и структурирования кода.

person Rick Minerich    schedule 18.09.2008

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

В первом примере Baz и Bar — это классы, которые можно наследовать и создавать экземпляры. Во втором примере Baz и Bar являются методами.

Вы не можете применять ссылки на интерфейс только к любому классу, который соответствует контракту интерфейса. Класс должен явно объявить, что он поддерживает интерфейс. Вы можете применить ссылку делегата к любому методу, который соответствует сигнатуре.

Вы не можете включать статические методы в контракт интерфейса. (Хотя вы можете использовать статические методы с помощью методов расширения). Вы можете ссылаться на статические методы с помощью ссылки на делегат.

person Amy B    schedule 18.09.2008

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

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

Взгляните на класс List‹> с методом Find. Теперь вы можете определить, что определяет, является ли что-то совпадением или нет, не требуя, чтобы элементы, содержащиеся в классе, реализовывали IListFindable или что-то подобное.

person Darren Kopp    schedule 18.09.2008

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

person kemiller2002    schedule 18.09.2008

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

person GHad    schedule 18.09.2008

Да, делегат можно рассматривать как интерфейс с одним методом.

person Ben Hoffstein    schedule 18.09.2008

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

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

person Wedge    schedule 18.09.2008

Интерфейсы и делегаты — две совершенно разные вещи, хотя я понимаю искушение описывать делегаты терминами, подобными интерфейсу, для простоты понимания… однако незнание истины может привести к путанице в будущем.

Делегаты были вдохновлены (частично) чёрной магией указателей на методы C++, которые не подходили для определённых целей. Классический пример — реализация механизма передачи сообщений или обработки событий. Делегаты позволяют вам определять сигнатуру метода без каких-либо знаний о типах или интерфейсах класса — я мог бы определить делегата «void eventHandler (Event * e)» и вызвать его для любого класса, который его реализовал.

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

person Dan    schedule 18.09.2008

По крайней мере, в одном предложении по добавлению замыканий (то есть анонимных делегатов) в Java они эквивалентны интерфейсам с одним методом-членом.

person Daniel Earwicker    schedule 05.03.2009