Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
При назначении метода делегату ковариация и контравариантность обеспечивают гибкость сопоставления типа делегата с сигнатурой метода. Ковариация позволяет методу иметь тип возвращаемого значения, который является более производным, чем определенный в делегате. Контравариантность допускает использование метода, параметры которого имеют типы, менее производные, чем типы в делегате.
Пример 1. Ковариация
Описание
В этом примере показано, как делегаты могут использоваться с методами, имеющими типы возвращаемых данных, производные от возвращаемого типа в сигнатуре делегата. Тип данных, возвращаемый DogsHandler, имеет тип Dogs, который является производным от типа Mammals, определенного в делегате.
Код
class Mammals {}
class Dogs : Mammals {}
class Program
{
// Define the delegate.
public delegate Mammals HandlerMethod();
public static Mammals MammalsHandler()
{
return null;
}
public static Dogs DogsHandler()
{
return null;
}
static void Test()
{
HandlerMethod handlerMammals = MammalsHandler;
// Covariance enables this assignment.
HandlerMethod handlerDogs = DogsHandler;
}
}
Пример 2. Контравариантность
Описание
В этом примере показано, как делегаты могут использоваться с методами, имеющими параметры, типы которых являются базовыми типами типа параметра подписи делегата. С помощью контравариантности можно использовать один обработчик событий вместо отдельных обработчиков. В следующем примере используется два делегата:
Настраиваемый
KeyEventHandlerделегат, определяющий сигнатуру ключевого события. Его подпись:public delegate void KeyEventHandler(object sender, KeyEventArgs e)Настраиваемый
MouseEventHandlerделегат, определяющий сигнатуру события мыши. Его подпись:public delegate void MouseEventHandler(object sender, MouseEventArgs e)
В примере определяется обработчик событий с параметром EventArgs и используется для обработки событий ключа и мыши. Это работает, так как EventArgs это базовый тип пользовательских KeyEventArgs и MouseEventArgs классов, определенных в примере. Контравариантность позволяет методу, который принимает параметр базового типа, быть использованным для событий, предоставляющих параметры производного типа.
Как работает контравариантность в этом примере
При подписке на событие компилятор проверяет, совместим ли метод обработчика событий с подписью делегата события. С контравариантностью:
- Событие
KeyDownожидает метод, который принимаетKeyEventArgs. - Событие
MouseClickожидает метод, который принимаетMouseEventArgs. - Метод
MultiHandlerпринимает базовый типEventArgs. - Поскольку
KeyEventArgsиMouseEventArgsоба наследуют отEventArgs, их можно безопасно передать в метод, ожидающийEventArgs. - Компилятор разрешает это назначение, так как оно безопасно —
MultiHandlerможет работать с любымEventArgsэкземпляром.
Это контравариантность в действии: можно использовать метод с параметром "менее конкретный" (базовый тип), где ожидается параметр "более конкретный" (производный тип).
Код
// Custom EventArgs classes to demonstrate the hierarchy
public class KeyEventArgs(string keyCode) : EventArgs
{
public string KeyCode { get; set; } = keyCode;
}
public class MouseEventArgs(int x, int y) : EventArgs
{
public int X { get; set; } = x;
public int Y { get; set; } = y;
}
// Define delegate types that match the Windows Forms pattern
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public delegate void MouseEventHandler(object sender, MouseEventArgs e);
// A simple class that demonstrates contravariance with events
public class Button
{
// Events that expect specific EventArgs-derived types
public event KeyEventHandler? KeyDown;
public event MouseEventHandler? MouseClick;
// Method to simulate key press
public void SimulateKeyPress(string key)
{
Console.WriteLine($"Simulating key press: {key}");
KeyDown?.Invoke(this, new KeyEventArgs(key));
}
// Method to simulate mouse click
public void SimulateMouseClick(int x, int y)
{
Console.WriteLine($"Simulating mouse click at ({x}, {y})");
MouseClick?.Invoke(this, new MouseEventArgs(x, y));
}
}
public class Form1
{
private Button button1;
public Form1()
{
button1 = new Button();
// Event handler that accepts a parameter of the base EventArgs type.
// This method can handle events that expect more specific EventArgs-derived types
// due to contravariance in delegate parameters.
// You can use a method that has an EventArgs parameter,
// although the KeyDown event expects the KeyEventArgs parameter.
button1.KeyDown += MultiHandler;
// You can use the same method for an event that expects
// the MouseEventArgs parameter.
button1.MouseClick += MultiHandler;
}
// Event handler that accepts a parameter of the base EventArgs type.
// This works for both KeyDown and MouseClick events because:
// - KeyDown expects KeyEventHandler(object sender, KeyEventArgs e)
// - MouseClick expects MouseEventHandler(object sender, MouseEventArgs e)
// - Both KeyEventArgs and MouseEventArgs derive from EventArgs
// - Contravariance allows a method with a base type parameter (EventArgs)
// to be used where a derived type parameter is expected
private void MultiHandler(object sender, EventArgs e)
{
Console.WriteLine($"MultiHandler called at: {DateTime.Now:HH:mm:ss.fff}");
// You can check the actual type of the event args if needed
switch (e)
{
case KeyEventArgs keyArgs:
Console.WriteLine($" - Key event: {keyArgs.KeyCode}");
break;
case MouseEventArgs mouseArgs:
Console.WriteLine($" - Mouse event: ({mouseArgs.X}, {mouseArgs.Y})");
break;
default:
Console.WriteLine($" - Generic event: {e.GetType().Name}");
break;
}
}
public void DemonstrateEvents()
{
Console.WriteLine("Demonstrating contravariance in event handlers:");
Console.WriteLine("Same MultiHandler method handles both events!\n");
button1.SimulateKeyPress("Enter");
button1.SimulateMouseClick(100, 200);
button1.SimulateKeyPress("Escape");
button1.SimulateMouseClick(50, 75);
}
}
Ключевые моменты о контравариантности
// Demonstration of how contravariance works with delegates:
//
// 1. KeyDown event signature: KeyEventHandler(object sender, KeyEventArgs e)
// where KeyEventArgs derives from EventArgs
//
// 2. MouseClick event signature: MouseEventHandler(object sender, MouseEventArgs e)
// where MouseEventArgs derives from EventArgs
//
// 3. Our MultiHandler method signature: MultiHandler(object sender, EventArgs e)
//
// 4. Contravariance allows us to use MultiHandler (which expects EventArgs)
// for events that provide more specific types (KeyEventArgs, MouseEventArgs)
// because the more specific types can be safely treated as their base type.
//
// This is safe because:
// - The MultiHandler only uses members available on the base EventArgs type
// - KeyEventArgs and MouseEventArgs can be implicitly converted to EventArgs
// - The compiler knows that any EventArgs-derived type can be passed safely
При запуске этого примера вы увидите, что один и тот же MultiHandler метод успешно обрабатывает события ключа и мыши, демонстрируя, как контравариантность обеспечивает более гибкий и многократно используемый код обработки событий.