Ссылка на класс или делегат

Рейтинг: 2Ответов: 2Опубликовано: 26.01.2015

Привет всем.

По мере углубления в изучении языка c# все больше задаюсь вопросом, для чего нужно это или то. Так как я самоучка и пишу программу методом тыка, в моих кодах хакер ногу сломит. Но все же пишу программки. Конечно, может, я их пишу неправильно, но конечный результат - все работает.

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

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

Заранее спасибо!

Ответы

▲ 3Принят

Небольшой пример про делегаты

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

Типичный пример с сортировками.

namespace ConsoleApplication1
{
    public delegate void TypeSort(ref int[] arr);

    static public class SortMethod{
        static public void Inc(ref int[] arr)
        {
            int j, k;
            for (int i = 0; i < arr.Length - 1; i++)
            {
                j = 0;
                do
                {
                    if (arr[j] > arr[j + 1])
                    {
                        k = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = k;
                    }
                    j++;
                }
                while (j < arr.Length - 1);
            }
        }

        static public void Dec(ref int[] arr)
        {
            int j, k;
            for (int i = 0; i < arr.Length - 1; i++)
            {
                j = 0;
                do
                {
                    if (arr[j] < arr[j + 1])
                    {
                        k = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = k;
                    }
                    j++;
                }
                while (j < arr.Length - 1);
            }
        }

        static public void Write(ref int[] arr)
        {
            Console.Write("Массив: ");
            foreach (int i in arr)
                Console.Write("{0}\t", i);
            Console.WriteLine();
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            int[] myArr = new int[6] { 2, -4, 10, 5, -6, 9 };

            TypeSort typesort = new TypeSort(SortMethod.Write);
            typesort += SortMethod.Inc;
            typesort += SortMethod.Write;
            typesort += SortMethod.Dec;
            typesort += SortMethod.Write;

            typesort(ref myArr);

            Console.Read();
        }
    }
}

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

Небольшой пример про интерфейсы

Другой дело с интерфейсами, они нужны для четкого описания того, что есть в классе. К примеру, вы делаете приложение, которое выполняет подключение к БД. Вам надо сделать подключение в трем типам СУБД по выбору: MySQL, SQL SERVER и Oracle. Вы могли бы делать для каждого свой класс и таскать их, но проще таскать интерфейс, не зная реализации класса.

public interface IDBConnect
{
    public bool Query(String query);
}

public class MSSQL : IDBConnect
{
    public bool Query(string query)
    {
        Console.WriteLine("Запрос к MSSQL");
        return true;
    }
}

public class MySQL : IDBConnect
{
    public bool Query(string query)
    {
        Console.WriteLine("Запрос к MySQL");
        return true;
    }
}

public class Oracle : IDBConnect
{
    public bool Query(string query)
    {
        Console.WriteLine("Запрос к Oracle");
        return true;
    }
}

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

public class useConnect
{
    IDBConnect connect;

    useConnect(IDBConnect con)
    {
        connect = con;
    }

    public void useQuery()
    {
        connect.Query("Запрос");
    }
}

public class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Выберите базу:");
        String DBase = Console.ReadLine();

        UseConnect connect;
        switch(DBase){
            case "MSSQL":
                connect = new UseConnect(new MSSQL());
                break;
            case "MySQL":
                connect = new UseConnect(new MySQL());
                break;
            default:
                connect = new UseConnect(new Oracle());
                break;
        }

        /* теперь нашему connect все равно, что там за база на самом деле */
        connect.useQuery();

        Console.Read();
    }
}

К тому же стоит заметить, что очень легко добавить новую базу (или абсолютно полностью переписать старую), унаследовав интерфейс и не меняя весь остальной код. Еще тут выполняется полиморфизм, вы всегда знаете, какие методы может иметь тот или иной класс, глядя на его интерфейс. Это также позволяет Вам писать свои классы на этом интерфейсе. Часто используемый пример - это встроенные IQueryable и IEnumerable, унаследовавшись от которых, ваши собственные классы могут реализовать правильное поведение "Как массив" для обращений по индексу myClass[1] и "Как перечисление" для foreach(var in myClass) { ... }.

Ну и поскольку тут поднималась тема про книги, то вот хороший список:

Классические книги по C#/.NET

UPD

В дополнение, часто используются делегаты для подписывания (и отписывания с помощью операции -=) на события.

public delegate void EventComplete();
class First{
    public EventComplete Complete;

    public void Do(){
        Thread.Sleep(2000);
        Complete();
    }
}

class Second
{
    public void Ok()
    {
        Console.WriteLine("Класс First выполнил действие");
    }
}

public class Program
{
    static void Main(string[] args)
    {
        First obj = new First();
        obj.Complete += new EventComplete( new Second().Ok );
        obj.Complete += new EventComplete( new Second().Ok );
        obj.Do();
    }
}

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

▲ 3

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

Однако, как показывает практика, такой подход приводит к довольно громоздкому коду.

Сравните:

C#/WPF

void SetupUpdate(DispatcherTimer timer)
{
    timer.Tick += (o, args) =>
        {
            var currentSeconds = DateTime.Now.Second;
            textBlock.Text = string.Format("{0} sec", currentSeconds);
        };
}

Аналог на Java/Swing

void SetupUpdate(javax.swing.Timer timer) {
    final JTextField innerTextField = textField; // для замыкания
    timer.AddActionListener(
        new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                Calender now = Calendar.getInstance();
                int seconds = now.get(Calendar.SECOND);
                innerTextField.setText(String.format("%d sec", seconds));
            }
    );
}

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