You'd simply use the [Guid] attribute on the interface and the class:
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("{7447EEEA-3D48-4D20-80DF-739413718794}")]
public interface IFoo {
[DispId(42)] void method();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("{40815257-BFD2-43D9-9CF8-FB27CC884C71}")]
[ProgId("Acme.Foo")]
public class Foo : IFoo {
public void method() { /* etc */ }
}
And in the AssemblyInfo.cs file:
[assembly: Guid("B75B31AD-D96A-473F-94E0-37E59847B997")]
Which covers the DispId of the interface members, the IID of the interfaces, the CLSID of the coclass, the ProgId of the coclass and the LIBID of the type library.
However, I would like to avoid actually writing those interfaces
You can use ClassInterfaceType.AutoDispatch to avoid writing the interfaces but that achieves the exact opposite of what you asked for. You can no longer control the IID anymore, you'll expose the System.Object methods which gives the client a type library dependency on mscorlib.tlb and every change you make will break the client. Very convenient to you, extremely inconvenient to your clients. But read on:
the class GUID are invariant what ever changes I make to them
I showed you how to do this. But this is actually a very strong anti-pattern in COM. Which demands that you change the IID when you make changes. Not changing it causes extremely nasty problems at runtime when the change you make is breaking. Very easy to do, just inserting or removing a method or changing the return type or arguments of a method are enough. Okay when the client code late-binds through IDispatch, fatal when it early-binds. The client will call the completely wrong method. Or you'll get arbitrary garbage values for the arguments. The client will crash with an AccessViolationException when it's lucky, next to impossible to diagnose why. It is not lucky then the call succeeds but just completely fails to operate correctly, utterly impossible to diagnose.
Don't do it.