C# interfaces can declare events, allowing consumers to subscribe to events without knowing the concrete type. The challenge is wiring the interface event to the actual event in the implementation — this is done with explicit add and remove accessors that forward subscriptions to the underlying event in the base class.
Delegate and EventArgs
csharp
// Custom EventArgs and delegate for money-transfer notifications
public class MoneyTransferredArgs : EventArgs
{
public double ValueTransferred { get; set; }
}
public delegate void MoneyTransferHandler(object sender, MoneyTransferredArgs args);Base Class
The abstract base holds the real event field and the method that fires it:
csharp
// Abstract base class that fires the event and provides a default handler
public abstract class Bank
{
public event MoneyTransferHandler MoneyTransferred;
public Bank()
{
MoneyTransferred += (sender, args) =>
Console.WriteLine(quot;Money Transferred: {args.ValueTransferred}");
}
public virtual void TransferMoney(double amount)
{
MoneyTransferred?.Invoke(this, new MoneyTransferredArgs { ValueTransferred = amount });
}
}Child Implementation
Each concrete class exposes OnMoneyTransferred as required by IBankLocation. The add and remove accessors forward subscriptions to the base class event, so only one invocation list exists:
csharp
// Interface declares the event using the same delegate type.
// The add/remove accessors delegate to the base class event,
// keeping a single underlying event subscription list.
public interface IBankLocation
{
event MoneyTransferHandler OnMoneyTransferred;
void TransferMoney(double amount);
}
public class BankLocation1 : Bank, IBankLocation
{
public event MoneyTransferHandler OnMoneyTransferred
{
add => MoneyTransferred += value;
remove => MoneyTransferred -= value;
}
}
public class BankLocation2 : Bank, IBankLocation
{
public event MoneyTransferHandler OnMoneyTransferred
{
add => MoneyTransferred += value;
remove => MoneyTransferred -= value;
}
}Usage
csharp
static void Main(string[] args)
{
IBankLocation bankRo1 = new BankLocation1();
IBankLocation bankRo2 = new BankLocation2();
// Attach handlers through the interface — no cast needed
bankRo1.OnMoneyTransferred += (s, e) => Console.WriteLine("Sent via BankRo1");
bankRo2.OnMoneyTransferred += (s, e) => Console.WriteLine("Sent via BankRo2");
bankRo1.TransferMoney(20);
bankRo2.TransferMoney(21);
}
// Output:
// Money Transferred: 20
// Sent via BankRo1
// Money Transferred: 21
// Sent via BankRo2