So aus dem Gedächtnis:
| 1 | public class Cmd
 | 
| 2 | {
 | 
| 3 |   public EventHandler<DataReceivedEventArgs> DataReceived;
 | 
| 4 | 
 | 
| 5 |   protected virtual void OnDataReceived(DataReceivedEventArgs e)
 | 
| 6 |   {
 | 
| 7 |     this.DataReceived()?.Invoke(e);
 | 
| 8 |   }
 | 
| 9 | 
 | 
| 10 |   public CommandoRawData ProcessCommando(Commando_Struct input)
 | 
| 11 |   {
 | 
| 12 |     ...
 | 
| 13 |     this.OnDataReceived(new DataReceivedEventArgs(CalculatedValues));
 | 
| 14 |     ...
 | 
| 15 |   }
 | 
| 16 | }
 | 
| 17 | 
 | 
| 18 | 
 | 
| 19 | public class Form1
 | 
| 20 | {
 | 
| 21 |   public Form1()
 | 
| 22 |   {
 | 
| 23 |     var cmd = new Cmd();
 | 
| 24 |     cmd.DataReceived += this.DataReceivedEventHandler;
 | 
| 25 |   }
 | 
| 26 | 
 | 
| 27 |   private void DataReceivedEventHandler(object sender, DataReceivedEventArgs e)
 | 
| 28 |   {
 | 
| 29 |     this.wsq.Voltage = e.Voltage;
 | 
| 30 |   }
 | 
| 31 | }
 | 
| 32 | 
 | 
| 33 | 
 | 
| 34 | public class DataReceivedEventArgs : EventArgs
 | 
| 35 | {
 | 
| 36 |   public DataReceivedEventArgs(CommandoRawData data)
 | 
| 37 |   {
 | 
| 38 |     this.Data = data;
 | 
| 39 |   }
 | 
| 40 | 
 | 
| 41 |   public CommandoRawData Data { get; }
 | 
| 42 | }
 | 
Du deklarierst in deiner Cmd-Klasse einen EventHandler (z.B. 
DataReceived) vom Typ EventHandler<DataReceivedEventArgs>. 
DataReceivedEventArgs ist dabei eine Hilfsklasse, die die Daten kapselt, 
die du über das Event weitergeben willst. Falls du keine Daten 
weitergeben willst, kannst du auch einfach EventHandler<EventArgs> 
schreiben.
Üblicherweise gibt es dann eine Methode "On<EventName>" die für das 
eigentliche Auslösen des Events zuständig ist. Diese wird dann einfach 
zu dem Zeitpunkt aufgerufen, wenn das Event ausgelöst werden soll.
Wichtig ist, dass diese Methode das Event nur auslöst (Aufruf von 
Invoke()), wenn der EventHandler ungleich null ist (ansonsten gibt es 
eine Exception). Ab C# 7 (?) kann das einfach über den .? Operator 
sichergestellt werden. Damit ist übrigens auch die 
Multithreading-Problematik abgehandelt, die Abradolf L. meinte.
ACHTUNG: Der Code in der verlinkten Doku (in OnCountdownCompleted) ist 
NICHT für Multithreading-Umgebungen geeignet. Zwischen der Prüfung auf 
null und der Invocation könnte Code, der in einem anderen Thread 
ausgeführt wird, sich vom Event abhängen. Wenn das dann der 
letzte/einzige EventHandler war, ist CountdownCompleted plötzlich doch 
null und es knallt.
In deiner Form registrierts du dann nur noch eine Methode am Event, die 
ausgeführt werden soll, wenn das Event auftritt. Dieser werden deine 
DataReceivedEventArgs übergeben, über die du auf deine Daten Zugriff 
hast.