Jump to content

Unity и С#: Что такое делегаты простыми словами


Recommended Posts

 

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

 

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

Что такое делегаты в c#?

Если простыми словами, то представьте, что вы написали код программы (класса), где в случае каких-либо действий на консоль выводятся сообщения. Действий и ситуаций, которые приводят к выводу много, соответственно, везде вы прописали например: "Debug.Log ("текст" + какаятопеременная);". И теперь вы можете использовать этот класс, как подпрограмму в основной программе. Но в одним момент поняли, что теперь у вас приложение будет не в консоль выводить текст, а на файл или на экран, как это сделать, не меняя код уже готового класса?

 

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

 

Это упрощенное понимание делегатов в c#, и функционал у них больше, но для начала стоит понять хотя бы это. Как же теперь будет работать наша программа? Если раньше она выводила какие-либо переменные на консоль, то теперь она это может делать в файл, либо на печать, либо еще куда-то. И как именно это будет решает уже внешний код, вызывающий класс.

 

Другими словами, когда мы разбрасывали по программе метки и маячки (то есть делегаты), то мы создавали "пустые" функции, которые потом можно подменить конкретными. Но только с определенной сигнатурой, например, у нашем примере они принимают переменную типа string, а возвращают void. Теперь все маячки и метки, у которых одинаковое имя, мы можем подменить функцией, определенной во внешнем коде, главное, чтобы она также принимала string и возвращала void.

 

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

Детали реализации делегатов в c#

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

Экземпляр делегата создается так TypeMyDelegat EkzemplyarName;, то есть этим кодом мы создали экземпляр делегата с именем EkzemplyarName и типом TypeMyDelegat. Мы можем создать несколько экземпляров с разными именами, которые будут иметь один и тот же тип (и сигнатуру, соответственно).

В коде вызывать (использовать, "ставить") экземпляры делегатов можно неограниченное число раз, точно также, как можно любое количество раз вызывать какую-либо функцию. Ведь по сути экземпляр делегата в c# это ссылка на функцию.

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

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

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

 

Создадим делегат с другим типом, например, delegate void TypeMyDelegatForMessage (string soobshenie), он не возвращает ничего, но принимает string-переменную, в нашем случае для вывода сообщения логов.

Допустим у нас в классе, где создан тип делегата с его экземплярами, есть функция void SohranitLogi(string soobshenie);, эта функция будет принимать от кода одну переменную типа string и как-то ее обрабатывать, например, выводить на консоль. Мы хотим, чтобы теперь расставленные по коду делегаты выполняли эту функцию. Пишем EkzemplyarNameForMessage = SohranitLogi;, теперь когда в коде встретится "команда" EkzemplyarNameForMessage (infa);, то будет вызвана функция void SohranitLogi(string soobshenie), в которую передается переменная infa.

 

Итого, все вышенаписанное может выглядеть так:

 

delegate void TypeMyDelegat(); // создаем тип (вид, породу) делегата, который ничего не принимает и не возвращает
TypeMyDelegat EkzemplyarName; //создаем экземпляр (объект) делегата, и даем ему имя EkzemplyarName, но мы к нему не приписали ссылку на какую-либо фукнцию, поэтому при попытке его вызвать будет Null и ошибка
//EkzemplyarName(); - такой код вызвал бы ошибку


delegate void TypeMyDelegatForMessage (string soobshenie); // создаем новый тип делегата, который может принимать string
TypeMyDelegatForMessage EkzemplyarNameForMessage; //создаем объект (экземпляр) делегата, вышеописанного типа, но еще без ссылки на функцию
void SohranitLogi(string soobshenie) //фукнция, которую будем вызывать через делегат
{
//... тут какой-то код, выводящий сообщение куда-то
}

EkzemplyarNameForMessage = SohranitLogi; // теперь экземпляр делегата EkzemplyarNameForMessage (у которогот тип TypeMyDelegatForMessage) будет содержать ссылку на функцию  SohranitLogi

EkzemplyarNameForMessage (dannie); // эта конструкция передаст в фукнцию  SohranitLogi переменную dannie. Тут мы используем экземпляр делегата, который ссылается на функцию.

Как передать делегат внутрь класса

Вернемся к нашему самому первому примеру про класс. Во внешнем коде мы определяем тип делегата (то есть пишем это "delegate void TypeMyDelegatForMessage (string soobshenie)") , а уже внутри класса должен быть создан внутренний экземпляр этого типа делегата, который мы раскидаем по коду в местах, где потребуется вызывать внешнюю функцию.

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

Выглядит это так, пишем внутри класса функцию: 

 

public void ZapihatbVdelegat (TypeMyDelegatForMessage 4toZapihat)

{

EkzemplyarNameForMessage = 4toZapihat; //пихаем во внутренний экземпляр делегата (по имени EkzemplyarNameForMessage) ссылку на функцию, которую нам передаст внешний экземпляр делегата через переменную 4toZapihat

}

 

Это функция внутри класса, которую можно вызывать из внешнего кода, и передать ей в качестве параметра через переменную экземпляр делегата с типом TypeMyDelegatForMessage. Данная функция, получив с внешней стороны ссылку на внешнюю функцию, запихает ее в свой внутренний экземпляр делегата. Это значит, что во внешнем коде мы должны будем иметь функцию, и также во внешнем коде мы должны создать экземпляр делегата, и назначить ему эту внешнюю функцию. И только после этого мы вызовем ZapihatbVdelegat и передадим ей наш экземпляр делегата с внешнего кода.

 

И один важный момент, теперь необходимо объявлять тип делегата (то есть писать это "delegate void TypeMyDelegatForMessage (string soobshenie)") во внешнем коде. Ведь мы там сначала создадим экземпляр делегата этого типа, там же в него запихнем нашу внешнюю функцию, и после этого уже передадим его в внутренний код класса через через функцию ZapihatbVdelegat.

Link to post
Share on other sites
 

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

Powered by Invision Community
Поддержка Invision Community в России

×
  • Create New...