**Advice I - `where TPriority : notnull`**

I would add `where TPriority : notnull` to the class declaraction, since the priority keys are always required.

**Note I**

I like your hack:

    BubbleUp(index);
    BubbleDown(index);

At most one of the two above calls will actually bubble up or down. By the way, it is conventional to call `BubbleX` as `SiftX`.

**Advice II - performance**

You could squeeze some CPU cycles by relying on an array of heap nodes, each heap node containing the item, priority and the index of the item, instead of a `List`.

**Note II - slight incorretness**

If you run the following:
```
var q = new MyPriorityQueue<int, int>();

q.Enqueue(1, 1);
q.Enqueue(1, 1);
q.EnqueueOrUpdate(1, 0);
q.EnqueueOrUpdate(1, 2);

Console.WriteLine("Size: {0}.", q.Count);

bool b = q.TryDequeue(out int e, out int p);

Console.WriteLine("{0}: {1}, {2}.", b, e, p);
Console.WriteLine("Size: {0}.", q.Count);

b = q.TryDequeue(out e, out p);

Console.WriteLine("{0}: {1}, {2}.", b, e, p);
Console.WriteLine("Size: {0}.", q.Count);
```
you will get:
```
Size: 2.
True: 1, 1. <--- Should be "True: 1, 0."
Size: 1.
True: 1, 2.
Size: 0.
```

**Advice III - alternative API**

I suggest you rip an interface (call it, for example, `PriorityQueue`) since there are several priority queue data types: binary heap, \\$d\\$-ary heap, binomial heap, Fibonacci heap, pairing heap.

The interface in question is:
```
namespace CR.MyPriorityQueue
{
    internal interface PriorityQueue<TElement, TPriority>
    {
        void Enqueue(TElement element, TPriority priority);
        bool Dequeue(out TElement element, out TPriority priority);
    }
}
```
Above, if the element to enqueue does not appear in the queue, it is added. Otherwise, its priority is updated.

**Summa summarum**

All in all, I had the following rewrite in mind. I start with your rewrite:
```using System.Collections.Generic;

namespace CR.PriorityQueues
{
    class MyPriorityQueue<TElement, TPriority> : PriorityQueue<TElement, TPriority>
    where TElement : notnull
    {
        private readonly List<(TElement Element, TPriority Priority)> heap = new();
        private readonly Dictionary<TElement, int> itemIndices = new();

        public MyPriorityQueue() => Comparer = Comparer<TPriority>.Default;
        public MyPriorityQueue(IComparer<TPriority> comparer) => Comparer = comparer;

        public int Count => heap.Count;
        public IComparer<TPriority> Comparer { get; }

        public void Enqueue(TElement item, TPriority priority)
        {
            if (itemIndices.TryGetValue(item, out int index))
            {
                heap[index] = (item, priority);
                BubbleUp(index);
                BubbleDown(index);
            }
            else
            {
                heap.Add((item, priority));
                itemIndices[item] = Count - 1;
                BubbleUp(Count - 1);
            }
        }

        public bool Dequeue(out TElement element, out TPriority priority)
        {
            if (Count == 0)
            {
                (element, priority) = (default, default);
                return false;
            }

            var minItem = heap[0];
            heap[0] = heap[^1];
            itemIndices[heap[0].Element] = 0;

            heap.RemoveAt(Count - 1);
            if (Count > 0)
            {
                BubbleDown(0);
            }

            (element, priority) = minItem;
            return true;
        }

        private void BubbleUp(int index)
        {
            while (index > 0)
            {
                var parentIndex = (index - 1) / 2;
                if (Compare(index, parentIndex))
                {
                    Swap(index, parentIndex);
                    index = parentIndex;
                }
                else
                {
                    break;
                }
            }
        }

        private void BubbleDown(int index)
        {
            int smallestIndex;

            while (true)
            {
                var leftChildIndex = 2 * index + 1;
                var rightChildIndex = 2 * index + 2;
                smallestIndex = index;

                if (leftChildIndex < Count && Compare(leftChildIndex, smallestIndex))
                {
                    smallestIndex = leftChildIndex;
                }

                if (rightChildIndex < Count && Compare(rightChildIndex, smallestIndex))
                {
                    smallestIndex = rightChildIndex;
                }

                if (smallestIndex == index)
                {
                    break;
                }

                Swap(index, smallestIndex);
                index = smallestIndex;
            }
        }

        private bool Compare(int leftIndex, int rightIndex) =>
            Comparer.Compare(heap[leftIndex].Priority, heap[rightIndex].Priority) < 0;

        private void Swap(int leftIndex, int rightIndex)
        {
            (heap[leftIndex], heap[rightIndex]) = (heap[rightIndex], heap[leftIndex]);

            itemIndices[heap[leftIndex].Element] = leftIndex;
            itemIndices[heap[rightIndex].Element] = rightIndex;
        }
    }
}
```
Next, I will show your what I had in mind:
```
namespace CR.PriorityQueues
{
    class CoderoddeBinaryHeap<TElement, TPriority> : PriorityQueue<TElement, TPriority>
        where TElement : notnull
        where TPriority : notnull
    {
        private static readonly int INITIAL_CAPACITY = 16;

        private class HeapNode<Element, Priority> where Element : notnull where Priority : notnull
        {
            public Element element;
            public Priority priority;
            public int index;

            public HeapNode(Element element, Priority priority)
            {
                this.element = element;
                this.priority = priority;
            }

            public string ToString()
            {
                return "[" + element.ToString() + " - " + priority.ToString() + ", index: " + index + "]";
            }
        }

        private int size;
        private HeapNode<TElement, TPriority>[] heapNodeArray =
            new HeapNode<TElement, TPriority>[INITIAL_CAPACITY];

        private readonly Dictionary<TElement, HeapNode<TElement, TPriority>> map = new();

        public IComparer<TPriority> Comparer { get; }

        public CoderoddeBinaryHeap()
        {
            Comparer = Comparer<TPriority>.Default;
        }

        public CoderoddeBinaryHeap(IComparer<TPriority> comparer)
        {
            Comparer = comparer;
        }

        public int Count => size;

        public void Enqueue(TElement item, TPriority priority)
        {
            if (map.TryGetValue(item, out var node))
            {
                node.priority = priority;
                SiftUp(node.index);
                SiftDown(node.index);
            }
            else
            {
                if (IsFull())
                {
                    ExtendHeapNodeArray();
                }

                HeapNode<TElement, TPriority> newNode = new HeapNode<TElement, TPriority>(item, priority);
                newNode.index = size;
                heapNodeArray[size] = newNode;
                size++;
                SiftUp(size - 1);
                map[item] = newNode;
            }
        }

        public bool Dequeue(out TElement element, out TPriority priority)
        {
            if (size == 0)
            {
                (element, priority) = (default, default);
                return false;
            }

            HeapNode<TElement, TPriority> node = heapNodeArray[0];
            size--;
            heapNodeArray[0] = heapNodeArray[size];
            node.index = 0;
            SiftDown(0);
            (element, priority) = (node.element, node.priority);
            heapNodeArray[size] = null;

            if (ShouldShrink())
            {
                ShrinkHeapNodeArray();
            }

            return true;
        }

        private void SiftUp(int index)
        {
            HeapNode<TElement, TPriority> node = heapNodeArray[index];
            TPriority priority = node.priority;

            while (index > 0)
            {
                int parentIndex = (index - 1) / 2;
                HeapNode<TElement, TPriority> parentNode = heapNodeArray[parentIndex];

                if (Comparer.Compare(priority, parentNode.priority) < 0)
                {
                    heapNodeArray[index] = parentNode;
                    parentNode.index = index;
                    index = parentIndex;
                }
                else
                {
                    break;
                }
            }

            heapNodeArray[index] = node;
            node.index = index;
        }
        private void SiftDown(int index)
        {
            HeapNode<TElement, TPriority> node = heapNodeArray[index];
            TPriority priority = node.priority;

            while (true)
            {
                int minimumChildIndex;
                int leftChildIndex = 2 * index + 1;

                if (leftChildIndex < size)
                {
                    minimumChildIndex = leftChildIndex;
                }
                else
                {
                    heapNodeArray[index] = node;
                    node.index = index;
                    return;
                }

                int rightChildIndex = leftChildIndex + 1;

                if (rightChildIndex < size && Compare(rightChildIndex, leftChildIndex))
                {
                    minimumChildIndex = rightChildIndex;
                }

                if (Comparer.Compare(priority, heapNodeArray[minimumChildIndex].priority) > 0)
                {
                    heapNodeArray[index] = heapNodeArray[minimumChildIndex];
                    heapNodeArray[index].index = index;

                    index = minimumChildIndex;
                    leftChildIndex = index * 2 + 1;
                    rightChildIndex = leftChildIndex + 1;
                }
                else
                {
                    heapNodeArray[index] = node;
                    node.index = index;
                    return;
                }
            }
        }

        private bool IsFull()
        {
            return size == heapNodeArray.Length;
        }

        private void ExtendHeapNodeArray()
        {
            HeapNode<TElement, TPriority>[] newHeapNodeArray =
            new HeapNode<TElement, TPriority>[heapNodeArray.Length * 2];
            Array.Copy(heapNodeArray, 0, newHeapNodeArray, 0, size);
            heapNodeArray = newHeapNodeArray;
        }

        private bool ShouldShrink()
        {
            return heapNodeArray.Length / 2 != INITIAL_CAPACITY &&
                4 * size <= heapNodeArray.Length;
        }

        private void ShrinkHeapNodeArray()
        {
            HeapNode<TElement, TPriority>[] newHeapNodeArray =
            new HeapNode<TElement, TPriority>[heapNodeArray.Length / 2];
            Array.Copy(heapNodeArray,
                       0,
                       newHeapNodeArray,
                       0,
                       newHeapNodeArray.Length);

            heapNodeArray = newHeapNodeArray;
        }

        private bool Compare(int leftIndex, int rightIndex) =>
            Comparer.Compare(heapNodeArray[leftIndex].priority,
                             heapNodeArray[rightIndex].priority) < 0;
    }
}
```

(The entire benchmark program is in [my gist](https://gist.github.com/coderodde/5df03dc69ebee96b5f4aa6f6aef2130b).)

## Typical output ##
```
Seed = -264809311.
MyPriorityQueue`2.Enqueue() in 625 milliseconds.
MyPriorityQueue`2.TryDequeue() in 1941 milliseconds.
Total MyPriorityQueue`2 duration: 2566 milliseconds.

CoderoddeBinaryHeap`2.Enqueue() in 835 milliseconds.
CoderoddeBinaryHeap`2.TryDequeue() in 450 milliseconds.
Total CoderoddeBinaryHeap`2 duration: 1285 milliseconds.

Algorithms agree: True.

MyPriorityQueue`2 is sorted: True.
CoderoddeBinaryHeap`2 is sorted: True.
```

Hope that helps.