대리자에게 메서드를 할당하면 공변성 및 반공변성이 메서드 시그니처와 대리자 형식을 일치시키는 유연성을 제공합니다. 공변성을 통해 메서드는 대리자에 정의된 반환 형식보다 더 파생된 반환 형식을 사용할 수 있습니다. 반공변성은 대리자 타입의 매개변수 타입보다 덜 파생된 타입을 사용하는 메서드를 허용합니다.
예제 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