Repeated construction using the default constructor shouldn't have any ill effects for performance or randomness quality.
Let's take a look at the source code...
#if !FEATURE_PAL
[System.Security.SecuritySafeCritical] // auto-generated
public RNGCryptoServiceProvider() : this((CspParameters) null) {}
#else // !FEATURE_PAL
public RNGCryptoServiceProvider() { }
#endif // !FEATURE_PAL
The FEATURE_PAL directive has to do with windows vs. non-windows platforms. But we don't need to know the details; let's just look at both true and false cases.
First, it's clear that if FEATURE_PAL is enabled, there's no code in the default constructor.
In the other case, the constructor calls a specific constructor with null CspParameters. That other constructor looks like this:
[System.Security.SecuritySafeCritical] // auto-generated
public RNGCryptoServiceProvider(CspParameters cspParams) {
if (cspParams != null) {
m_safeProvHandle = Utils.AcquireProvHandle(cspParams);
m_ownsHandle = true;
}
else {
m_safeProvHandle = Utils.StaticProvHandle;
m_ownsHandle = false;
}
}
cspParams will always be null, so the constructor is getting the value of Utils.StaticProvHandle
. That getter looks like this:
#if !FEATURE_PAL
[System.Security.SecurityCritical /*auto-generated*/]
private static SafeProvHandle _safeProvHandle = null;
internal static SafeProvHandle StaticProvHandle {
[System.Security.SecurityCritical] // auto-generated
get {
if (_safeProvHandle == null) {
lock (InternalSyncObject) {
if (_safeProvHandle == null) {
SafeProvHandle safeProvHandle = AcquireProvHandle(new CspParameters(DefaultRsaProviderType));
Thread.MemoryBarrier();
_safeProvHandle = safeProvHandle;
}
}
}
return _safeProvHandle;
}
}
#endif // !FEATURE_PAL
It's backed by a static variable. The getter uses some locks during first initialization, but subsequent calls simply return the static variable.
Now let's look back at RNGCryptoServiceProvider.cs and look at the Dispose method:
[System.Security.SecuritySafeCritical] // auto-generated
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
if (disposing && m_ownsHandle) {
m_safeProvHandle.Dispose();
}
}
m_ownsHandle
is false if the default constructor was called, so it never disposes anything.
So all that happens during each construction+disposal is some simple variable access.