You can actually release your Excel Application object cleanly, but you do have to take care.
The advice to maintain a named reference for absolutely every COM object you access and then explicitly release it via Marshal.FinalReleaseComObject() is correct in theory, but, unfortunately, very difficult to manage in practice. If one ever slips anywhere and uses "two dots", or iterates cells via a for each loop, or any other similar kind of command, then you'll have unreferenced COM objects and risk a hang. In this case, there would be no way to find the cause in the code; you would have to review all your code by eye and hopefully find the cause, a task that could be nearly impossible for a large project.
The good news is that you do not actually have to maintain a named variable reference to every COM object you use. Instead, call GC.Collect() and then GC.WaitForPendingFinalizers() to release all the (usually minor) objects to which you do not hold a reference, and then explicitly release the objects to which you do hold a named variable reference.
You should also release your named references in reverse order of importance: range objects first, then worksheets, workbooks, and then finally your Excel Application object.
For example, assuming that you had a Range object variable named "xlRng", a Worksheet variable named "xlSheet", a Workbook variable named "xlBook" and an Excel Application variable named "xlApp", then your cleanup code could look something like the following:
// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);
xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);
xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);
In most code examples you'll see for cleaning up COM objects from .NET, the GC.Collect() and GC.WaitForPendingFinalizers() calls are made TWICE as in:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
This should not be required, however, unless you are using Visual Studio Tools for Office (VSTO), which uses finalizers that cause an entire graph of objects to be promoted in the finalization queue. Such objects would not be released until the next garbage collection. However, if you are not using VSTO, you should be able to call GC.Collect() and GC.WaitForPendingFinalizers() just once.
I know that explicitly calling GC.Collect() is a no-no (and certainly doing it twice sounds very painful), but there is no way around it, to be honest. Through normal operations you will generate hidden objects to which you hold no reference that you, therefore, cannot release through any other means other than calling GC.Collect().
This is a complex topic, but this really is all there is to it. Once you establish this template for your cleanup procedure you can code normally, without the need for wrappers, etc. :-)
I have a tutorial on this here:
Automating Office Programs with VB.Net / COM Interop
It's written for VB.NET, but don't be put off by that, the principles are exactly the same as when using C#.