대리자에게 메서드를 할당하면 공변성 및 반공변성이 메서드 시그니처와 대리자 형식을 일치시키는 유연성을 제공합니다. 공변성을 통해 메서드는 대리자에 정의된 반환 형식보다 더 파생된 반환 형식을 사용할 수 있습니다. 반공변성은 대리자 타입의 매개변수 타입보다 덜 파생된 타입을 사용하는 메서드를 허용합니다.
예제 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 정의하고 이를 사용하여 키 및 마우스 이벤트를 모두 처리합니다. 이는 예제에 정의된 사용자 지정 KeyEventArgs
및 MouseEventArgs
클래스의 기본 형식이기 때문에 EventArgs 작동합니다. 반공변성에서는 파생 형식 매개 변수를 제공하는 이벤트에 기본 형식 매개 변수를 허용하는 메서드를 사용할 수 있습니다.
이 예제에서 반공변성 작동 방식
이벤트를 구독할 때 컴파일러는 이벤트 처리기 메서드가 이벤트의 대리자 서명과 호환되는지 확인합니다. 반공변성:
- 이벤트는
KeyEventArgs
를 취하는KeyDown
메서드를 요구합니다. - 이벤트에는
MouseEventArgs
를 받아들이는MouseClick
메서드가 필요합니다. -
MultiHandler
메서드는 기본 형식EventArgs
을 사용합니다. -
KeyEventArgs
및MouseEventArgs
은 모두EventArgs
를 상속하므로,EventArgs
을(를) 기대하는 메서드에 안전하게 전달될 수 있습니다. - 컴파일러는 안전하므로 이 할당을 허용합니다. 모든
EventArgs
인스턴스에서MultiHandler
작동할 수 있습니다.
이는 동작의 반공변성입니다. "보다 구체적인" (파생 형식) 매개 변수가 필요한 "덜 구체적인"(기본 형식) 매개 변수가 있는 메서드를 사용할 수 있습니다.
코드
// 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
메서드가 키 및 마우스 이벤트를 모두 성공적으로 처리하여 반공변성으로 보다 유연하고 재사용 가능한 이벤트 처리 코드를 사용하는 방법을 보여 줍니다.
참고하십시오
.NET