Ironically, you have actually got a few things wrong here.
First of all, delegates are not just function pointers. They're bound method pointers, meaning that they can capture the receiver of the call in addition to the method itself. This makes it possible to create a delegate to an instance method, which is not something you can do with function pointers alone in C++ (there, you need to define a data structure that combines the function pointer with the object pointers; or use something like mem_fn or bind that'll do it for you).
More importantly, when you say that "events are just function pointer lists", this is actually what delegates are, rather than events. Given your definition of Foo, for example, it is possible to say:
Foo f = ...
Foo g = ...
Foo h = f + g; // look, a delegate that references two different methods!
In the most common case, at least when passing them around as arguments, a delegate object will refer to a single method. But it can refer to arbitrarily many methods of different objects, and it's still denoted by the same delegate type. The rules defining what happens when you invoke such a delegate are not obvious (in particular, with respect to return values and out/ref parameters) - order matters, for example, and so do repeated inclusions of the same method (i.e. it's not a set, it's an ordered list).
(In theory, it is possible to have delegate types that are not lists, if you define them as inheriting from Delegate rather than MulticastDelegate on IL level - this is legal per CLI spec. But no .NET language that I know of provides the ability to define such types in practice, so you'd need to use something like ilasm.)
Events, on the other hand, are an entirely different concept altogether. The most crucial difference between an event and a delegate is that event does not define a type, nor is it an object. An event is simply a pattern of two accessor methods - one to add an event handler (represented as a delegate), the other to remove a handler - formalized on language level. When you write something like:
public event EventHandler ShitHappened;
what it does is define two methods named add_ShitHappened and remove_ShitHappened, both taking an argument of type EventHandler. Because you didn't specify the implementation of those methods, it will also automatically generate them as if you wrote it like this:
private EventHandler ShitHappened;
public event EventHandler ShitHappened {
add { ShitHappened += value; }
remove { ShitHappened -= value; }
}
(In practice it actually uses atomic operations to ensure that handlers can be registered concurrently from different threads without one overwriting the other, but I've omitted that for the sake of simplicity. For single-threaded case, the above code is equivalent.)
You can't actually write it like that yourself, because it's not legal to have the same name for the field and the event - but for automatically generated accessors it is legal, and distinguishing between them depends on the context where it's done. If you're referencing ShitHappened from the same class, then you're accessing the field. If you're accessing it outside the class, you're accessing the event.
Either way, the important part is that, regardless of whether the event has implicitly defined accessors or not, users of the class only see the add/remove accessors, and do not have access to the backing storage. This means that they cannot, for example, raise the event from the outside, or enumerate the list of subscribers and directly invoke some of them, or remove a delegate from that list that they haven't themselves added (or obtained from elsewhere, which is why it's a good idea to make all event handlers private).
And that is the sole purpose of event: to provide encapsulation for the observer pattern that ensures that code outside of the class can only register and unregister its own handlers, and cannot interfere with handlers from other components, or violate the contract of the class by raising its event at inappropriate times. If you just need a list of function pointers (that anyone can enumerate, mutate and call at will), then that's what delegates are for.
Rough TL;DR version: events are to fields of delegate types what properties are to fields of other types. Both exist for the purpose of encapsulation. An event is not "a list of delegates", though it is a very popular misconception.
Also, while the concepts itself are sound, the actual implementation of all that stuff in CLR and C# could really be better. In particular, the fact that there's Delegate and MulticastDelegate, but then all delegate types actually derive from the latter (and there are no non-multicast delegates in practice). And also the confusion between the event and its backing field for the auto-generated event accessors - it's very common for C# newbies to be confused by the fact that they can do "x.ShitHappened != null" if they're inside the class, but not if they're outside. IMO, they should have completely encapsulated the backing field (like they do for auto-properties), and treat ShitHappened(...) as a special syntax on the event instead of just a direct field access - then they could also add automatic thread-safe null checks that you always have to do anyway. IIRC, that's exactly what VB does.