We spent a fair amount of time arguing about what to name this type. In the end, we decided it was better to follow the pattern we established with Int16, Int32, and Int64, that is IntSize. In this case Size is the size of a pointer, which is 32 on a 32-bit machine and 64 on a 64-bit machine.
Even though it has a custom signature like Int32 and Double, it is fair to say that IntPtr is not a first class base datatype. Many programming languages such as C# and VB.Net do not support literals of type IntPtr, nor do they support arithmetic operations even though such operations are supported in the IL instruction set. In addition, some areas of the runtime don't support IntPtr. For example it cannot be used for the underlying type of an enum. I think this is a reasonable design decision, even in light of the transition to 64-bit computing, because it turns out IntPtr is not a good replacement for Int32 in most cases as many programs do need to know the size of the data type they are working with.
IntPtr is a super common type for interop scenarios, but is not used very frequently outside of interop. As such it would have been better to have this type in the System.Runtime.InteropServices namespace.
While common for interop scenarios, IntPtr also gets a fair workout in the code generation (Reflection.Emit and the new Lightweight Code Generation [LCG] APIs in CLR 2.0) namespaces. We can use it to represent things like method code pointers for IL instructions like "calli". You can call RuntimeMethodHandle.Get-Function Pointer() which will hand you back a System.IntPtr.
It's also found under the hood of the delegate type implementation.
When you use IntPtr to represent an operating system handle (HWND, HKEY, HDC, etc.), it's far too easy to be vulnerable to leaks, lifetime issues, or handle recycling attacks. The .NET Framework's System.Runtime.InteropServices.HandleRef class can be used in place of IntPtr to address lifetime issues; it guarantees that the managed object wrapping the handle won't be collected until the call to unmanaged code finishes. But to help you battle all three issues, look for a new type to be added to the .NET Framework in the future called SafeHandle. Once this is available, it should be used wherever you used IntPtr or HandleRef to represent a handle.
IntPtr is of course the bare minimum type you need to represent handles in PInvoke calls because it is the correct size to represent a handle on all platforms, but it isn't what you want for a number of subtle reasons. We came up with two hacky versions of handle wrappers in our first version (HandleRef and the not-publicly exposed HandleProtector class), but they were horribly incomplete and limited. I've long wanted a formal OS Handle type of some sort, and we finally designed one in our version 2 release called SafeHandle.
Another interesting point is a subtle race condition that can occur when you have a type that uses a handle and provides a finalizer. If you have a method that uses the handle in a PInvoke call and never references this after the PInvoke call, then the this pointer may be considered dead by our GC. If a garbage collection occurs while you are blocked in that PInvoke call (such as a call to ReadFile on a socket or a file), the GC could detect the object was dead, then run the finalizer on the finalizer thread. You'll get unexpected results if your handle is closed while you're also trying to use it at the same time, and these races will only get worse if we add multiple finalizer threads. To work around this problem, you can stick calls to GC.KeepAlive(this); in your code after your PInvoke call, or you could use HandleRef to wrap your handle and the this pointer.
Of course, the SafeHandle class (added in version 2) solves this problem and five others, most of which can't be fully appreciated without understanding thread aborts and our reliability story. See my comments on the AppDomain class for more details.