CodeGym /Courses /C# SELF /Recommendations for organizing code

Recommendations for organizing code

C# SELF
Level 54 , Lesson 1
Available

1. Style of declaring and naming events

Events are not just delegates. They are a distinct entity for communication between parts of the application, and their declaration should be clear.

Use the right delegate type

In 99% of cases use the standard delegates:

  • EventHandler — for events without data.
  • EventHandler<TEventArgs> — when you need to pass parameters.

Standardization makes code maintenance and integration with .NET libraries easier. Don't invent your own delegate if EventHandler fits.


public event EventHandler SomethingHappened; // No data
public event EventHandler<MyEventArgs> DataReceived; // Has additional data

If you need special customization — declare your own delegate, but that's rare.

Event naming

In .NET events are named in the past tense: Completed, Clicked, Changed, Received. That emphasizes that something has happened.

Examples:


public event EventHandler DataLoaded;    // Data has been loaded
public event EventHandler<MessageEventArgs> MessageReceived; // Message received
public event EventHandler Saving;        // The saving process has started

Sometimes the form Changing is used for "before" change events to give a chance to intervene.

2. Organizing the publisher class: the protected virtual OnEvent method

Always add a protected virtual method that raises the event: it gives a centralized call point, extensibility in inheritance, and predictable behavior.


public class FileLoader
{
    public event EventHandler<FileLoadedEventArgs> FileLoaded;

    protected virtual void OnFileLoaded(FileLoadedEventArgs e)
    {
        FileLoaded?.Invoke(this, e);
    }

    public void Load(string filename)
    {
        // ... file loading logic ...
        OnFileLoaded(new FileLoadedEventArgs(filename));
    }
}

public class FileLoadedEventArgs : EventArgs
{
    public string FileName { get; }
    public FileLoadedEventArgs(string fileName) => FileName = fileName;
}

Let only OnFileLoaded raise the event — it's easier to maintain and test that way.

3. Subscription and unsubscription rules: lifecycle, IDisposable

If the subscriber's lifetime is shorter than the publisher's, be sure to unsubscribe before the subscriber is destroyed. It's convenient to implement IDisposable and unsubscribe in Dispose().


public class TemporaryListener : IDisposable
{
    private readonly Publisher _publisher;

    public TemporaryListener(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.DataReceived += HandleData;
    }

    private void HandleData(object sender, EventArgs e)
    {
        // Working with data
    }

    public void Dispose()
    {
        _publisher.DataReceived -= HandleData;
    }
}

// Usage with using:
using (var listener = new TemporaryListener(myPublisher))
{
    // listener listens to events here
}
// After exiting using - Dispose is called, unsubscribe happened

If you forget to unsubscribe, the publisher will hold a reference to the subscriber's delegate — you'll get memory leaks and "zombie objects".

4. Thread-safe event invocation

In multithreaded code subscribers can be added/removed concurrently with event invocation. This can cause races and NullReferenceException. Use the thread-safe pattern: copy the delegate to a local variable.


protected virtual void OnSomethingHappened()
{
    EventHandler handler = SomethingHappened;
    handler?.Invoke(this, EventArgs.Empty);
}

With C# 6+ it's enough:


SomethingHappened?.Invoke(this, EventArgs.Empty);

5. Use EventArgs instead of object

Don't pass data via object or class fields. Use strong typing through EventArgs descendants.


public class DownloadCompletedEventArgs : EventArgs
{
    public string FileName { get; }
    public long Size { get; }
    public DownloadCompletedEventArgs(string fileName, long size)
    {
        FileName = fileName;
        Size = size;
    }
}

public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;

6. Documenting events and subscribers

Document: when the event is raised, the meaning of EventArgs fields, whether unsubscription is needed and when.


/// <summary>
/// The event occurs after successful data load.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;

7. Summary recommendations for event architecture

Separate responsibilities

The publisher only notifies about the fact. The subscriber decides when to subscribe and unsubscribe.

Avoid "bombarding" with events

Don't raise the same event dozens of times per second without need — that's excessive load.

Avoid using events for two-way communication

Events are for "one reports — many listen" schemes. For two-way communication consider interfaces, callbacks or other mechanisms.

Don't store subscribers in the class

Don't keep explicit references to subscribers — events and delegates will do that automatically.

8. Classic antipatterns

Typeless events


public event Action<object> SomethingHappened; // It's unclear what's inside

Bad: typing is broken, casts are required, maintainability is lost.

Forgetting to unsubscribe


public class ShortLivedListener
{
    public ShortLivedListener(Publisher p) =>
        p.DataReceived += DoWork;

    private void DoWork(object sender, EventArgs e) { /* ... */ }
    // No Dispose, no unsubscribe => zombie objects!
}

Violating SRP

A class that is simultaneously publisher, subscriber, and handler — mixing roles. Separate responsibilities.

9. Practical use in interviews and projects

In many publish-subscribe projects proper organization of events is a key to scalability and maintainability. In interviews you're often asked to:

  • implement an event system with correct typing,
  • show lifecycle management of subscribers,
  • explain thread-safe event invocation.

Clean, documented, properly organized event code immediately sets you apart among candidates.

2
Task
C# SELF, level 54, lesson 1
Locked
Organizing events with multiple subscribers
Organizing events with multiple subscribers
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION