.NET is a platform on which you develop applications. It sits just above the OS and provides several services that make application development easier and more uniform. Probably its most important goal is to enable the seamless integration of software components. Other goals include language independance, simplified deployment and better security. Essentially, .NET provides a solid infrastructure to enable these goals.
The part of .NET, developers are most concerned with is the .NET framework. The .NET framework is two things... the Common Language Runtime (CLR) and the Base Class Library (BCL). The CLR is the virtual execution engine where your application runs. It takes care of loading your types (classes/structs/enums/etc...), verifying and enforcing type safety, providing a garbage collector to take care of memory management and also a Just-In-Time compiler (JITer) to compile your intermediate code to
native machine code. Any code running under the CLR is called managed code since it is in essence managed by the CLR. The BCL is a massive library that provides basic data types like int/double/string, collections like ArrayList/Hashtable/Queue/Stack, streams like Memory/File/Network and many more base services. On top of this basic framework are technologies like WinForms for developing windows client apps and WebForms for creating web apps (ASP .NET). And of course there are classes for Data Management (ADO .NET), XML, Remoting and Web Services.
All languages that target .NET produce Intermediate Language (IL) code, NOT native machine code. This is essentially how it achieves language independence. Whatever language you use, when you compile it, the compiler generates IL. Only when you run your application will the CLR load up your code and JIT compile it to native machine code. This is why there is a performance hit when you move to managed code from native (unmanaged) code. As with everything in software development, there are trade-offs. You get the vast services of the CLR and BCL, but you pay a price in efficiency.
An application is really just made up of components. Components in .NET are called assemblies. Assemblies are the smallest thing you can deploy... just like DLL's and EXE's pre .NET. An assembly contains two things... a manifest and one or more modules. The manifest contains everything about the assembly and its modules... including its identity (which involves its version number, some sort of unique hash and possibly a public/private encoding key), what external assemblies the modules refer to and also any resources it uses like images, files etc... among other things. The modules themselves contain IL code and metadata about what types (classes/structs/enums/etc...) it exposes and security permissions.
One thing to notice is that metadata (data about data) is very important in this system. The CLR makes heavy use of this in all aspects. For example, there is data in the assembly about security permissions. So when the CLR loads a particular assembly, it queries this security info and only if the proper permissions are asserted will it execute the code.
Every assembly is self describing. No other information is needed for it to "fit" within the system. There is no need for the registry anymore. There is no registering and unregistering DLL's in .NET. To install an app, you just copy the assemblies to any directory - that's it! Similarly, to uninstall, just delete the directory. Again, because of the richness of the metadata, DLL hell is all but history. You can have multiple versions of the same assembly running side-by-side on your machine. As long as one aspect of the assembly identity is different - be it the version number, the public/private key or hash - that's all that is needed.
Another very appealing aspect is that you can create configuration files to instruct the CLR what assemblies to load after any application has been deployed. When you create your application, you may use version 1 of a certain assembly. Later on, version 2 of that assembly is installed on the machine. You can set up a config file for your app which tells the CLR to use version 2 instead of version 1 of the assembly. You just change the config files... no need to change any of your code or re-installing your app. This is all done dynamically at runtime.
It seems to me that they have thought about the whole framwork quite thoroughly and have come up with a good platform on which to develop applications. A lot of this is very similar to Java, which has a very similar model. Infact, you could say they pretty much copied a lot of it... And they did! But that is the way it is. They have improved a lot of things over Java, just as Java improved on previous systems like Smalltalk.