Прежде всего, хороший вопрос. Я восхищаюсь вашим вниманием к полезности, а не слепо принимая «лучшие практики». +1 за это.
Я читал это руководство раньше. Вы должны вспомнить кое-что об этом - это просто руководство, в основном для новичков в C #, которые знают, как программировать, но не очень хорошо знакомы с тем, как C # делает что-то. Это не столько страница правил, сколько страница, которая описывает, как все обычно делается. И так как они уже сделали это повсюду, было бы неплохо оставаться последовательным.
Я доберусь до сути, отвечая на ваши вопросы.
Прежде всего, я предполагаю, что вы уже знаете, что такое интерфейс. Что касается делегата, достаточно сказать, что это структура, содержащая типизированный указатель на метод вместе с необязательным указателем на объект, представляющий this
аргумент для этого метода. В случае статических методов последний указатель является нулевым.
Есть также многоадресные делегаты, которые похожи на делегаты, но им может быть назначено несколько таких структур (то есть один вызов Invoke для многоадресного делегата вызывает все методы в его назначенном списке вызовов).
Что они подразумевают под образцом событийного дизайна?
Они подразумевают использование событий в C # (в котором есть специальные ключевые слова для сложной реализации этого чрезвычайно полезного шаблона). События в C # инициируются многоадресными делегатами.
Когда вы определяете событие, например, в этом примере:
class MyClass {
// Note: EventHandler is just a multicast delegate,
// that returns void and accepts (object sender, EventArgs e)!
public event EventHandler MyEvent;
public void DoSomethingThatTriggersMyEvent() {
// ... some code
var handler = MyEvent;
if (handler != null)
handler(this, EventArgs.Empty);
// ... some other code
}
}
Компилятор фактически преобразует это в следующий код:
class MyClass {
private EventHandler MyEvent = null;
public void add_MyEvent(EventHandler value) {
MyEvent += value;
}
public void remove_MyEvent(EventHandler value) {
MyEvent -= value;
}
public void DoSomethingThatTriggersMyEvent() {
// ... some code
var handler = MyEvent;
if (handler != null)
handler(this, EventArgs.Empty);
// ... some other code
}
}
Затем вы подписываетесь на событие, выполнив
MyClass instance = new MyClass();
instance.MyEvent += SomeMethodInMyClass;
Который сводится к
MyClass instance = new MyClass();
instance.add_MyEvent(new EventHandler(SomeMethodInMyClass));
Так что это происходит в C # (или .NET в целом).
Насколько легко получается состав, если используется делегат?
Это может быть легко продемонстрировано:
Предположим, у вас есть класс, который зависит от набора действий, которые ему нужно передать. Вы можете инкапсулировать эти действия в интерфейсе:
interface RequiredMethods {
void DoX();
int DoY();
};
И любой, кто хочет передать действия вашему классу, должен сначала реализовать этот интерфейс. Или вы можете сделать их жизнь проще , полагаясь на следующий класс:
sealed class RequiredMethods {
public Action DoX;
public Func<int> DoY();
}
Таким образом, вызывающие пользователи должны только создать экземпляр RequiredMethods и привязать методы к делегатам во время выполнения. Это является , как правило , легче.
Такой способ ведения дел чрезвычайно полезен при правильных обстоятельствах. Подумайте об этом - зачем зависеть от интерфейса, когда все, что вас действительно волнует, это передача вам реализации?
Преимущества использования интерфейсов при наличии группы связанных методов
Полезно использовать интерфейсы, потому что интерфейсы обычно требуют явных реализаций во время компиляции. Это означает, что вы создаете новый класс.
И если у вас есть группа связанных методов в одном пакете, полезно, чтобы этот пакет был повторно использован другими частями кода. Так что, если они могут просто создать экземпляр класса вместо создания набора делегатов, это проще.
Преимущества использования интерфейсов, если классу нужна только одна реализация
Как отмечалось ранее, интерфейсы реализуются во время компиляции - это означает, что они более эффективны, чем вызов делегата (который сам по себе является уровнем косвенности).
«Одна реализация» может означать реализацию, которая существует в одном четко определенном месте.
В противном случае реализация может происходить из любой точки программы, которая просто соответствует сигнатуре метода. Это обеспечивает большую гибкость, поскольку методы должны соответствовать только ожидаемой сигнатуре, а не принадлежать к классу, который явно реализует определенный интерфейс. Но такая гибкость может обойтись дорого и фактически нарушает принцип подстановки Лискова , потому что в большинстве случаев вам нужна четкость, потому что она сводит к минимуму вероятность несчастных случаев. Так же, как статическая печать.
Термин может также относиться к многоадресным делегатам здесь. Методы, объявленные интерфейсами, могут быть реализованы только один раз в классе реализации. Но делегаты могут накапливать несколько методов, которые будут вызываться последовательно.
В общем, похоже, что руководство не достаточно информативно, а просто функционирует так, как оно есть - руководство, а не свод правил. Некоторые советы могут показаться немного противоречивыми. Вам решать, когда будет правильно применять что. Руководство, кажется, только дает нам общий путь.
Я надеюсь, что ваши вопросы были удовлетворены. И снова, спасибо за вопрос.