I know this is old but I took the liberty of implementing* Jon Skeet's theoretical shuffle because it was a cool idea. The PcgRandom algorithm has a feature that allows one to "jump" to arbitrary points within the random number generator and this can be used to "remember" the values that were generated during the shuffle, and reverse it:
var currentIndex = m_currentIndex;
var lastIndex = (m_values.Length - 1);
while (-1 < currentIndex) {
    m_randomNumberGenerator.Jump(-1);
    var randomIndex = m_randomNumberGenerator.NextInt32(currentIndex, lastIndex);
    var tempValue = m_values[randomIndex];
    m_values[randomIndex] = m_values[currentIndex];
    m_values[currentIndex--] = tempValue;
    m_randomNumberGenerator.Jump(-1);
}
Here's the full implementation of the random number generator:
/// <summary>
/// Represents an instance of a fast random number generator; relies on the Pcg32XshRr algorithm described by Melissa O'neil.
/// </summary>
/// <remarks>
/// http://pcg-random.org/paper.html
/// https://en.wikipedia.org/wiki/Lehmer_random_number_generator
/// https://en.wikipedia.org/wiki/Linear_congruential_generator
/// https://github.com/lemire/fastrange
/// </remarks>
public sealed class FastRandom : IRandomNumberGenerator
{
    private const ulong MAX_STREAM_VALUE = ((1UL << 63) - 1UL);
    private readonly ulong m_magic;
    private readonly ulong m_stream;
    private ulong m_state;
    /// <summary>
    /// Gets the default <see cref="FastRandom"/> instance.
    /// </summary>
    public static readonly FastRandom Default = new FastRandom();
    /// <summary>
    /// Initializes a new instance of the <see cref="FastRandom"/> class.
    /// </summary>
    /// <param name="state">The initial state of the random number generator.</param>
    /// <param name="stream">The stream offset of the random number generator.</param>
    [CLSCompliant(false)]
    public FastRandom(ulong state, ulong stream) {
        if (stream > MAX_STREAM_VALUE) {
            throw new ArgumentOutOfRangeException(message: "stream offset must be a positive integer less than 2^63", paramName: nameof(stream));
        }
        m_magic = 6364136223846793005UL;
        m_state = state;
        m_stream = ((((~(stream & 1UL)) << 63) | stream) | 1UL);
    }
    /// <summary>
    /// Initializes a new instance of the <see cref="FastRandom"/> class.
    /// </summary>
    /// <param name="state">The initial state of the random number generator.</param>
    /// <param name="stream">The stream offset of the random number generator.</param>
    public FastRandom(long state, long stream) : this(checked((ulong)state), checked((ulong)stream)) { }
    /// <summary>
    /// Initializes a new instance of the <see cref="FastRandom"/> class.
    /// </summary>
    /// <param name="state">The initial state of the random number generator.</param>
    [CLSCompliant(false)]
    public FastRandom(ulong state) : this(state, SecureRandom.Default.NextUInt64(0UL, MAX_STREAM_VALUE)) { }
    /// <summary>
    /// Initializes a new instance of the <see cref="FastRandom"/> class.
    /// </summary>
    /// <param name="state">The initial state of the random number generator.</param>
    public FastRandom(long state) : this((ulong)state) { }
    /// <summary>
    /// Initializes a new instance of the <see cref="FastRandom"/> class.
    /// </summary>
    public FastRandom() : this(SecureRandom.Default.NextUInt64()) { }
    /// <summary>
    /// Moves the state of the generator forwards by the specificed number of steps.
    /// </summary>
    /// <param name="count">The number of states that will be jumped.</param>
    [CLSCompliant(false)]
    public void Jump(ulong count) {
        Jump(ref m_state, m_magic, m_stream, count);
    }
    /// <summary>
    /// Moves the state of the generator forwards or backwards by the specificed number of steps.
    /// </summary>
    /// <param name="count">The number of states that will be jumped.</param>
    public void Jump(long count) {
        Jump(ref m_state, m_magic, m_stream, unchecked((ulong)count));
    }
    /// <summary>
    /// Generates a uniformly distributed double precision floating point value between the exclusive range (0, 1).
    /// </summary>
    public double NextDouble() {
        return Operations.DoornikDouble(NextInt32(), NextInt32());
    }
    /// <summary>
    /// Generates a uniformly distributed 32-bit signed integer between the range of int.MinValue and int.MaxValue.
    /// </summary>
    public int NextInt32() {
        return ((int)NextUInt32());
    }
    /// <summary>
    /// Generates a uniformly distributed 32-bit signed integer between the inclusive range [x, y].
    /// </summary>
    /// <param name="x">The value of x.</param>
    /// <param name="y">The value of y.</param>
    public int NextInt32(int x, int y) {
        if (x > y) {
            var z = x;
            x = y;
            y = z;
        }
        var range = ((uint)(y - x));
        if (range != uint.MaxValue) {
            return ((int)(Sample(ref m_state, m_magic, m_stream, exclusiveHigh: (range + 1U)) + x));
        }
        else {
            return NextInt32();
        }
    }
    /// <summary>
    /// Generates a uniformly distributed single precision floating point value between the exclusive range (0, 1).
    /// </summary>
    public float NextSingle() {
        return Operations.DoornikSingle(NextInt32());
    }
    /// <summary>
    /// Generates a uniformly distributed 32-bit unsigned integer between the range of uint.MinValue and uint.MaxValue.
    /// </summary>
    [CLSCompliant(false)]
    public uint NextUInt32() {
        return Next(ref m_state, m_magic, m_stream);
    }
    /// <summary>
    /// Generates a uniformly distributed 32-bit unsigned integer between the inclusive range [x, y].
    /// </summary>
    /// <param name="x">The value of x.</param>
    /// <param name="y">The value of y.</param>
    [CLSCompliant(false)]
    public uint NextUInt32(uint x, uint y) {
        if (x > y) {
            var z = x;
            x = y;
            y = z;
        }
        var range = (y - x);
        if (range != uint.MaxValue) {
            return (Sample(ref m_state, m_magic, m_stream, exclusiveHigh: (range + 1U)) + x);
        }
        else {
            return NextUInt32();
        }
    }
    private static void Jump(ref ulong state, ulong magic, ulong stream, ulong count) {
        var accMul = 1UL;
        var accAdd = 0UL;
        var curMul = magic;
        var curAdd = stream;
        while (count > 0UL) {
            if (0UL < (count & 1UL)) {
                accMul *= curMul;
                accAdd = ((accAdd * curMul) + curAdd);
            }
            curAdd = ((curMul + 1UL) * curAdd);
            curMul *= curMul;
            count >>= 1;
        }
        state = ((accMul * state) + accAdd);
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static uint Next(ref ulong state, ulong magic, ulong stream) {
        state = unchecked((state * magic) + stream);
        return Operations.RotateRight(value: ((uint)(((state >> 18) ^ state) >> 27)), count: ((int)(state >> 59)));
    }
    private static uint Sample(ref ulong state, ulong magic, ulong stream, uint exclusiveHigh) {
        ulong sample = Next(ref state, magic, stream);
        ulong result = (sample * exclusiveHigh);
        uint leftover = ((uint)result);
        if (leftover < exclusiveHigh) {
            uint threshold = ((((uint)(-exclusiveHigh)) % exclusiveHigh));
            while (leftover < threshold) {
                sample = Next(ref state, magic, stream);
                result = (sample * exclusiveHigh);
                leftover = ((uint)result);
            }
        }
        return ((uint)(result >> 32));
    }
}
And the full implementation of the shuffle logic:
/// <summary>
/// Represents a strongly typed collection that uses a random number generator to retrieve elements.
/// </summary>
/// <typeparam name="T">The type of elements encapsulated by the bag.</typeparam>
public sealed class ShuffleBag<T> : IEnumerable<T>, IEnumerator<T>
{
    private readonly IRandomNumberGenerator m_randomNumberGenerator;
    private readonly T[] m_values;
    private int m_currentIndex;
    /// <summary>
    /// Gets the element in the collection at the current position of the enumerator.
    /// </summary>
    object IEnumerator.Current => Current;
    /// <summary>
    /// Gets the element in the collection at the current position of the enumerator.
    /// </summary>
    public T Current => m_values[m_currentIndex];
    /// <summary>
    /// Initializes a new instance of the <see cref="ShuffleBag{T}"/> class.
    /// </summary>
    /// <param name="randomNumberGenerator">The source of random numbers that will be used to perform the shuffle.</param>
    /// <param name="values">The array of values that will be randomized.</param>
    public ShuffleBag(IRandomNumberGenerator randomNumberGenerator, T[] values) {
        if (randomNumberGenerator.IsNull()) {
            throw new ArgumentNullException(message: "random number generator cannot be null", paramName: nameof(randomNumberGenerator));
        }
        if (values.IsNull()) {
            throw new ArgumentNullException(message: "array of values cannot be null", paramName: nameof(values));
        }
        m_currentIndex = -1;
        m_randomNumberGenerator = randomNumberGenerator;
        m_values = values;
    }
    /// <summary>
    /// Releases all resources used by this <see cref="ShuffleBag"/> instance.
    /// </summary>
    public void Dispose() { }
    /// <summary>
    /// Returns an enumerator that randomly yields elements from the bag.
    /// </summary>
    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }
    /// <summary>
    /// Returns an enumerator that randomly yields elements from the bag.
    /// </summary>
    public IEnumerator<T> GetEnumerator() {
        while (MoveNext()) {
            yield return Current;
        }
    }
    /// <summary>
    /// Advances the enumerator to the next random element in the collection.
    /// </summary>
    public bool MoveNext() {
        var currentIndex = m_currentIndex;
        var lastIndex = (m_values.Length - 1);
        if (currentIndex < lastIndex) {
            var randomIndex = m_randomNumberGenerator.NextInt32(++currentIndex, lastIndex);
            var tempValue = m_values[randomIndex];
            m_currentIndex = currentIndex;
            m_values[randomIndex] = m_values[currentIndex];
            m_values[currentIndex] = tempValue;
            return true;
        }
        else {
            return false;
        }
    }
    /// <summary>
    /// Sets the enumerator to its initial position.
    /// </summary>
    /// <param name="unshuffle">Indicates whether elements will be returned to their original, unshuffled, positions.</param>
    public void Reset(bool unshuffle) {
        if (unshuffle) {
            var currentIndex = m_currentIndex;
            var lastIndex = (m_values.Length - 1);
            while (-1 < currentIndex) {
                m_randomNumberGenerator.Jump(-1);
                var randomIndex = m_randomNumberGenerator.NextInt32(currentIndex, lastIndex);
                var tempValue = m_values[randomIndex];
                m_values[randomIndex] = m_values[currentIndex];
                m_values[currentIndex--] = tempValue;
                m_randomNumberGenerator.Jump(-1);
            }
        }
        m_currentIndex = -1;
    }
    /// <summary>
    /// Sets the enumerator to its initial position and returns elements to their original, unshuffled, positions.
    /// </summary>
    public void Reset() {
        Reset(unshuffle: true);
    }
}
*Sorry, it's in C#.