Чтобы дополнить полезный ответ Матиаса Р. Джессена - который вполне может быть лучшим решением вашей проблемы - ответом на ваш исходный вопрос:
Итак, что ЕСТЬ правильный способ (в PS 5.1) для создания единой структуры данных, на которую нужно ссылаться множеством классов и изменять некоторые из них [без заботы о безопасности потоков]?
Основная причина, по которой следует избегать глобальных переменных , заключается в том, что они являются сеансом -глобальным , что означает, что код, который выполняется после ваш собственный тоже видит эти переменные, которые могут иметь побочные эффекты.
Вы не можете реализовать настоящий синглтон в PowerShell , потому что классы PowerShell не поддерживают модификаторы доступа ; в частности, вы не можете сделать конструктор private (не publi c), вы можете только «скрыть» его с помощью ключевого слова hidden
, что просто делает его менее обнаруживаемым , в то время как по-прежнему доступен .
Вы можете приблизить синглтон с помощью следующей техники , которая сама имитирует stati c class (который PowerShell также не поддерживает, поскольку ключевое слово static
поддерживается только для членов class , а не для класса в целом).
Простой пример:
# NOT thread-safe
class AlmostAStaticClass {
hidden AlmostAStaticClass() { Throw "Instantiation not supported; use only static members." }
static [string] $Message # static property
static [string] DoSomething() { return ([AlmostAStaticClass]::Message + '!') }
}
[AlmostAStaticClass]::<member>
(например, [AlmostAStaticClass]::Message = 'hi'
) теперь можно использовать в области, в которой был определен AlmostAStaticClass
и все потомки scopes (но это не доступно глобально, если только определяющая область не является глобальной).
Если вам нужен доступ к классу через границы модуля , вы можете передать его как параметр (как литерал типа); обратите внимание, что вам по-прежнему требуется ::
для доступа к членам (всегда stati c); например,
& { param($staticClass) $staticClass::DoSomething() } ([AlmostAStaticClass])
Реализация потокобезопасного квази-одноэлементного - возможно, для использования с ForEach-Object -Parallel
(v7 +) или Start-ThreadJob
(v6 +, но устанавливается на v5.1) - требуется дополнительная работа :
Примечание:
Затем требуются методы для получения и установки того, что является концептуально свойствами, поскольку PowerShell не поддерживает методы получения и установки свойств с кодовой поддержкой, начиная с версии 7.0 (добавление этой возможности является предметом этот запрос функции GitHub ).
Однако вам все еще нужно базовое свойство , поскольку PowerShell не поддерживает поля ; опять же, лучшее, что вы можете сделать, это скрыть это свойство, но оно технически все еще доступно.
В следующем примере используется System.Threading.Monitor
(на котором основан оператор C# lock
) для управления поточно-безопасным доступом к значению; для управления одновременным добавлением и удалением элементов из коллекций используйте потокобезопасные типы коллекций из пространства имен System.Collections.Concurrent
.
# Thread-safe
class AlmostAStaticClass {
static hidden [string] $_message = '' # conceptually, a *field*
static hidden [object] $_syncObj = [object]::new() # sync object for [Threading.Monitor]
hidden AlmostAStaticClass() { Throw "Instantiation not supported; use only static members." }
static SetMessage([string] $text) {
Write-Verbose -vb $text
# Guard against concurrent access by multiple threads.
[Threading.Monitor]::Enter([AlmostAStaticClass]::_syncObj)
[AlmostAStaticClass]::_message = $text
[Threading.Monitor]::Exit([AlmostAStaticClass]::_syncObj)
}
static [string] GetMessage() {
# Guard against concurrent access by multiple threads.
# NOTE: This only works with [string] values and instances of *value types*
# or returning an *element from a collection* that is
# only subject to concurrency in terms of *adding and removing*
# elements.
# For all other (reference) types - entire (non-concurrent)
# collections or individual objects whose properties are
# themselves subject to concurrent access, the *calling* code
# must perform the locking.
[Threading.Monitor]::Enter([AlmostAStaticClass]::_syncObj)
$msg = [AlmostAStaticClass]::_message
[Threading.Monitor]::Exit([AlmostAStaticClass]::_syncObj)
return $msg
}
static [string] DoSomething() { return ([AlmostAStaticClass]::GetMessage() + '!') }
}
Обратите внимание, что, как и при пересечении границ модуля, использование потоков также требует передачи класса в качестве объекта типа другим потокам, что, однако, более удобно сделать с помощью спецификатора $using:
области видимости; простой (надуманный) пример:
# !! BROKEN AS OF v7.0
$class = [AlmostAStaticClass]
1..10 | ForEach-Object -Parallel { ($using:class)::SetMessage($_) }
Примечание : это перекрестное использование потоков фактически не работает с v7.0 из-за того, что классы в настоящее время связаны в , определяющее пространство выполнения - см. этот выпуск GitHub . Посмотрим, будет ли предоставлено решение.
Как видите, ограничения классов PowerShell делают реализацию такого сценария ios громоздкой; использование Add-Type
с ad ho c -компилированным кодом C# заслуживает рассмотрения в качестве альтернативы.
Эта мета-проблема GitHub представляет собой компиляцию различных проблем, связанных с классами PowerShell ; хотя в конечном итоге они могут быть разрешены, маловероятно, что классы PowerShell когда-либо достигнут паритета функций с C#; в конце концов, OOP не является фокусом языка сценариев PowerShell (кроме , использующего уже существующие объекты).