Почему Task и Task не sealed?

Почему майкрософт не убрали возможность наследования от Task и Task? Если не sealed, значит есть ситуации когда можно/правильно пронаследоваться от этого класса. Какие это могут быть в теории ситуации?


Ответы (2 шт):

Автор решения: Max S

А зачем их запечатывать? Как наследование от тасков может их сломать (если специально не стараться)?

Практика в .NET, хоть и на первый взгляд нелогична, но такова:

  1. Методы НЕ виртуальны по умолчанию
  2. Классы НЕ запечатаны по умолчанию

Task и Task<T> просто следуют этой же практике.

На деле же, эта практика вполне логична. Вы можете хотеть запечатать класс, когда у вас есть более высокоуровневый класс с виртуальными методами, которые вы переопределяете в своем классе, но дальнейшее наследование от вашего класса не является каким-то логичным и/или довольно тривиальным.

Кейс наследования тоже довольно просто придумать. Если вам нужен какой-нибудь, например, Task<Dictionary<MyClass, Dictionary<string, int>>, то вы можете написать MyTask : Task<Dictionary<MyClass, Dictionary<string, int>> и сократить себе запись в коде.

→ Ссылка
Автор решения: Pavel Mayorov

Задачи не запечатаны по той причине, что наследование от Task активно используется в недрах фреймворка. Вот примеры:

  1. Task.CancellationPromise<T> - задача, возвращаемая методом Task<T>.WaitAsync

  2. Task.DelayPromise - задача, возвращаемая методом Task.Delay

  3. Task.WhenAllPromise - задача, возвращаемая методом Task.WhenAll

  4. Stream.ReadWriteTask - задача, возвращаемая стандартными асинхронными методами потоков ввода-вывода

В принципе, от любого из этих наследников при желании можно избавиться, обойдясь внешним API (тот же WaitAsync неоднократно реализовывался разными костылями вокруг обычного API, а для всего остального есть TaskCompletionSource), однако использование особого внутреннего (internal) API совместно с наследованием позволяет коду из стандартной библиотеки аллоцировать меньше объектов.

Вспомним стандартный полифил для WaitAsync, чаще называемый WithCancellation:

        public static async Task WithCancellation(this Task task, CancellationToken cancellationToken)
        {
            var tcs = new TaskCompletionSource<bool>();
            using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
            {
                if (task != await Task.WhenAny(task, tcs.Task).ConfigureAwait(false))
                {
                    throw new OperationCanceledException(cancellationToken);
                }
                await task; // already completed; propagate any exception
            }
        }

Он создаёт аж 4 новых объекта в куче: TaskCompletionSource, CancellationTokenRegistration (это не объект, но внутри объект всё равно есть), результат WhenAny, и конечный автомат для async-метода.

Создателям же CancellationPromise удалось обойтись только самим CancellationPromise и CancellationTokenRegistration.

→ Ссылка