The Disposable Design Principle is built on these ideas:
Level 0 types directly wrap unmanaged resources. These types are generally sealed. only deal with unmanaged resources.
Level 1 types are types that derive from Level 1 types and/or contain field members that are Level 0 or Level 1 types. only deal with managed resources (defined by a base type and/or in fields).
Implementing IDisposable on Level 1 types is rather simple: just implement IDisposable.Dispose as calling Dispose on any IDisposable field, and then, if this type is derived from an IDisposable type, call base.Dispose. This is not the place for general shutdown logic. Note the following for this simple implementation:
Dispose is safe to be called multiple times because it is safe to call IDisposable.Dispose multiple times, and that's all it does.
Level 1 type should not have finalizers; they wouldn't be able to do anything anyway, since managed code cannot be accessed.
It is not necessary to call GC.KeepAlive(this) at the end of Dispose. Even though it is possible for the garbage collector to collect this object while Dispose is still running, this is not dangerous since all the resources being disposed are managed, and neither this type nor any derived types have finalizers.
Calling GC.SuppressFinalize(this) is likewise unnecessary because neither this type nor any derived types have finalizers.