Если вы хотите минимизировать давление ГХ, я бы предложил использовать интерфейсы, а не делегатов. Если вы используете IDisposable
, вы не можете избежать вызова Dispose
, но вы можете заставить реализацию IDisposable использовать флаг, чтобы указать, что метод Dispose
ничего не должен делать. Помимо того факта, что делегаты имеют некоторую встроенную языковую поддержку, на самом деле они ничего не могут сделать, что интерфейсы не могут, но интерфейсы предлагают два преимущества перед делегатами:
- Использование делегата, который связан с некоторыми данными, обычно требует создания объекта кучи для данных и второго для самого делегата. Интерфейсы не требуют второго экземпляра кучи.
- В обстоятельствах, когда можно использовать универсальные типы, которые ограничены интерфейсом, вместо непосредственного использования типов интерфейса, можно избежать создания каких-либо экземпляров кучи, как объяснено ниже (поскольку обратное форматирование не работает в элементы списка). Структура, которая объединяет делегат статического метода вместе с данными, которые будут использоваться этим методом, может вести себя как делегат, не требуя выделения кучи.
Одно предостережение со вторым подходом: хотя избегать давления GC - хорошая вещь, второй подход может привести к созданию очень большого количества типов во время выполнения. Количество создаваемых типов в большинстве случаев будет ограничено, но существуют обстоятельства, когда оно может увеличиваться без ограничений. Я не уверен, есть ли какой-нибудь удобный способ определить полный набор типов, которые могут быть созданы программой в случаях, когда статического анализа будет достаточно (в общем случае, когда статического анализа недостаточно, определяя, является ли какой-либо производимый тип во время выполнения был бы эквивалентен проблеме остановки, но так же, как многие программы на практике могут быть определены статически, чтобы всегда останавливаться или никогда не останавливаться, я ожидаю, что на практике часто можно идентифицировать закрытый набор типов, которые программа может производить во время выполнения).
Редактировать
Форматирование в пункте № 2 выше было испорчено. Вот объяснение, приведенное в порядок.
Определите тип ConditionalCleaner<T> : IDisposable
, который содержит экземпляр T
и Action<T>
(оба предоставляются в конструкторе - возможно, с Action<T>
в качестве первого параметра). В методе IDisposable.Dispose()
, если Action<T>
не равен NULL, вызовите его для T
. В методе SkipDispose()
обнулите Action<T>
. Для удобства вы можете захотеть также определить ConditionalCleaner<T,U>: IDisposable
аналогичным образом (возможно, версии с тремя и четырьмя аргументами), и вы можете определить статический класс ConditionalCleaner с помощью универсальных методов Create<T>
, Create<T,U>
и т. Д. ( так, например, можно сказать, например, using (var cc = ConditionalCleaner.Create(Console.WriteLine, "ABCDEF") {...}
или ConditionalCleaner.Create((x) => {Console.WriteLine(x);}, "ABCDEF")
, чтобы указанное действие выполнялось при выходе из блока using. Самое большое требование, если используется выражение Lambda, - убедиться, что лямбда-выражение не закрывается ни по каким локальным переменным или параметрам из вызывающей функции; все, что вызывающая функция хочет передать в лямбда-выражение, должно быть его явным параметром. В противном случае система определит объект класса для хранения любых закрытых переменных, а также новый делегат, указывающий на него.