Шаг 188 - Подробнее о делегатах

Артём в шагах "Шаг 97 - Указатель на функцию delegate" и "Шаг 98 - Извещения EVENT" описывал делегаты, я не буду повторятся лишь напомню, что это аналог функций обратного вызова(callback) в C/C++. Но это не совсем так, давайте рассмотрим небольшую программку.

using System;
internal class TestDelegate
{
	public delegate void DelegateFoo(object obj, int IntValue);
	public static void StaticFoo(object obj, int IntValue)
	{
		Console.WriteLine("что - то делаем");
	}
	public void InstatnceFoo(object obj, int IntValue)
	{
		Console.WriteLine("вводим данные {0} и {1}", obj, IntValue);
	}
	public DelegateFoo OurDelegate;
}
internal class App
{
	[STAThreadAttribute]
	public static void Main()
	{
		TestDelegate test = new TestDelegate();
		test.OurDelegate = new TestDelegate.DelegateFoo(TestDelegate.StaticFoo);
		test.OurDelegate("string", 128);
		test.OurDelegate += new TestDelegate.DelegateFoo(test.InstatnceFoo);
		test.OurDelegate("string", 128);
	}
}

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

gif/188_1.gif (23014 b)

Мы видим, что появился новый класс. Дело в следующем. Когда компилятор встречает

public delegate void DelegateFoo(object obj, int IntValue);

Он преобразует это в класс, в нашем случае он имеет вид

public class DelegateFoo : System.MulticastDelegate
{
	//конструктор
	public DelegateFoo(Object target, Int32 methodPtr);
	//прототип нашего метода
	public void virtual Invoke(Object obj, Int32 IntVlaue);
	//далее идут методы которые используются для 
	//"выполнения " делегата асинхронно
	//об этом я поговорю позже
	public virtual IAsyncResult BeginInvoke(
		Object obj, Int32 IntValue, 
		AsyncCallback callback, Object object);
	public virtual void EndInvoke(IAsyncResult result);
}

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

test.OurDelegate = new TestDelegate.DelegateFoo(TestDelegate.StaticFoo);

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

MulticastDlegate имеет два public read only свойства Target и Method. Target это проперти которое возвращает ссылку на объект, в котором определена функция которая используется делегатом(если метод статической то получим null). Method возвращает System.Reflection.MethodInfo который идентифицирует callback метод. Это можно использовать например для проверки типа делегата:

Boolean TestInstanceMethodOfType(
	MulticastDelegate d, Type type) {
		return((d.Target != null) && d.Target.GetType == type);
	}

Или для проверки имени метода:

Boolean TestMethodOfName(
	MulticastDelegate d, String methodName) {
		return(d.Method.Name == methodName);
	}

Если вы посмотрите на код в ILDASM вы увидите, что мы не вызываем наш метод следующим образом

test.OurDelegate("string", 128);

Вместо этого подставляется следующая строка(не совсем такая, это так написал чтобы понятнее было)

test.OurDelegate.Invoke("string", 128);

Про "Асинхронную модель программирования " я расскажу в следующей статье. Примеры использования Target и Method взяты из статьи Jeffrey Richter " An Introduction to Delegates ".


Загрузить проект | Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Leonid Molochniy.